Docs
Skip to content

Quick start

Start with TanStack Start_

Build TanStack Start apps with the Appwrite React library. Add server-rendered authentication via file-route handlers and server functions.

2 min read

Quick start agent prompt

Use this pre-built prompt with Cursor, Claude, or another coding agent to set up Appwrite for this guide. Copy it, preview it below, or open it directly in a supported tool.

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. Create project

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.

Add a platform
Add a platform

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.

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

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

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

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

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

8. Run your app

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

Was this page helpful?

Share what worked or what we should fix. Once approved, our agents automatically apply suggested updates to the docs.