---
layout: article
title: Start with TanStack Start
description: Build TanStack Start apps with the Appwrite React library. Add server-rendered authentication via file-route handlers and server functions.
difficulty: beginner
readtime: 7
back: /docs/quick-starts
---

Learn how to set up your first TanStack Start project with the [Appwrite React library](/docs/products/auth/react). The library exposes a TanStack file-route handler, server helpers, and the same React hooks you use on the client.

## 1. Create project

Head to the [Appwrite Console](https://cloud.appwrite.io/console).

![Create project screen](/images/docs/quick-starts/create-project.avif)

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](/blog/post/cors-error).

![Add a platform](/images/docs/quick-starts/add-platform.avif)

You can skip optional steps.

## 2. Create an API key

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 TanStack Start project

Create a TanStack Start project.

```sh
npx @tanstack/cli create my-app --framework react && cd my-app
```

## 4. Install the React library

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

```sh
npm install @appwrite.io/react appwrite node-appwrite @tanstack/react-query
```

## 5. Configure environment variables

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

```sh
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. Mount the auth handler route

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/*`.

```ts
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. Read auth state in a server function

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`.

```tsx
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. Run your app

```sh
npm run dev
```

Open [localhost on port 3000](http://localhost: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](/docs/products/auth/react).
