Skip to content

Start with TanStack Start

Use the pre-built prompt to get started faster

Learn how to set up your first TanStack Start project with the Appwrite React library. The library exposes a TanStack file-route handler, server helpers, and the same React hooks you use on the client.

1

Head to the Appwrite Console.

Create project screen

Create project screen

If this is your first time using Appwrite, create an account and create your first project.

Then, under Add a platform, add a Web app. The Hostname should be localhost.

Cross-Origin Resource Sharing (CORS)

Adding localhost as a platform lets your local app talk to Appwrite. For production, add your live domain to avoid CORS errors.

Learn more in our CORS error guide.

Add a platform

Add a platform

You can skip optional steps.

2

In your project, go to Overview > Integrations > API keys and create a new key with the scopes users.read, users.write, and sessions.write. Copy the key secret. The SSR handler uses this to create sessions on behalf of users; never expose it to the browser.

3

Create a TanStack Start project.

Shell
npx @tanstack/cli create my-app --framework react && cd my-app
4

Install the React library along with the Appwrite Web SDK, Appwrite Node SDK, and @tanstack/react-query packages.

Shell
npm install @appwrite.io/react appwrite node-appwrite @tanstack/react-query
5

Create a .env file at the project root. Replace <REGION>, <PROJECT_ID>, and <API_KEY> with your own values.

Shell
VITE_APPWRITE_ENDPOINT=https://<REGION>.cloud.appwrite.io/v1
VITE_APPWRITE_PROJECT_ID=<PROJECT_ID>
APPWRITE_API_KEY=<API_KEY>

VITE_* values are shipped to the browser. APPWRITE_API_KEY stays server-only.

6

Create src/routes/api/appwrite/$.ts. The TanStack file route exposes the library's sign-in, sign-up, sign-out, and oauth/callback endpoints under /api/appwrite/*.

TypeScript
import { createFileRoute } from "@tanstack/react-router";
import { createAppwriteHandlers } from "@appwrite.io/react/handlers/tanstack";

export const Route = createFileRoute("/api/appwrite/$")({
  server: {
    handlers: createAppwriteHandlers({
      endpoint: import.meta.env.VITE_APPWRITE_ENDPOINT,
      projectId: import.meta.env.VITE_APPWRITE_PROJECT_ID,
      apiKey: process.env.APPWRITE_API_KEY!,
      basePath: "/api/appwrite",
    }),
  },
});
7

Replace src/routes/index.tsx with the following. A createServerFn loader reads the session cookie server-side; the page receives the result via Route.useLoaderData() and passes it into AppwriteProvider.

React
import { useState } from "react";
import { createFileRoute, useRouter } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
import { AppwriteProvider, useAuth } from "@appwrite.io/react";
import { createTanStackServerHelpers } from "@appwrite.io/react/server/tanstack";

const getAuthSnapshot = createServerFn({ method: "GET" }).handler(async () => {
  const helpers = createTanStackServerHelpers({
    endpoint: import.meta.env.VITE_APPWRITE_ENDPOINT,
    projectId: import.meta.env.VITE_APPWRITE_PROJECT_ID,
  });
  return {
    session: helpers.readSessionCookie() ?? null,
    user: await helpers.getLoggedInUser(),
  };
});

export const Route = createFileRoute("/")({
  loader: () => getAuthSnapshot(),
  component: Page,
});

function Page() {
  const { session, user } = Route.useLoaderData();

  return (
    <AppwriteProvider
      endpoint={import.meta.env.VITE_APPWRITE_ENDPOINT}
      projectId={import.meta.env.VITE_APPWRITE_PROJECT_ID}
      ssr={{ session, basePath: "/api/appwrite" }}
    >
      <main>
        <h1>Appwrite React library on TanStack Start</h1>
        <p>SSR user: {user?.email ?? "signed out"}</p>
        <AuthPanel />
      </main>
    </AppwriteProvider>
  );
}

function AuthPanel() {
  const { user, isLoading, signIn, signUp, signOut, error } = useAuth();
  const router = useRouter();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [name, setName] = useState("");

  if (isLoading) return <p>Loading...</p>;

  if (user) {
    return (
      <div>
        <p>Welcome, {user.name || user.email}</p>
        <button onClick={() => signOut.signOut({ onSuccess: () => router.invalidate() })}>
          Sign out
        </button>
      </div>
    );
  }

  return (
    <div>
      <input placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} />
      <input placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} />
      <input
        placeholder="Password"
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button
        onClick={() =>
          signUp.emailPassword({
            email,
            password,
            name,
            onSuccess: () => router.invalidate(),
          })
        }
        disabled={signUp.isPending}
      >
        Sign up
      </button>
      <button
        onClick={() =>
          signIn.emailPassword({
            email,
            password,
            onSuccess: () => router.invalidate(),
          })
        }
        disabled={signIn.isPending}
      >
        Sign in
      </button>
      {error && <p style={{ color: "red" }}>{error.message}</p>}
    </div>
  );
}

After every auth mutation, router.invalidate() re-runs the loader so the SSR user reflects the new session cookie.

Multi-route apps

In a multi-route TanStack Start app, move AppwriteProvider (and the auth loader) into src/routes/__root.tsx so the TanStack Query cache and auth state persist across navigations. Mounting the provider in a single route works for this quickstart, but resets the cache every time the route changes.

8
Shell
npm run dev

Open localhost on port 3000. Sign up, sign out, and sign back in to verify the cookie-based SSR flow.

Next steps

For server-side admin operations, per-request session clients, OAuth callbacks, and the full hook reference, see the React library docs.