---
title: TanStack Start
description: SSR auth for TanStack Start with HttpOnly cookie sessions.
sidebar:
  icon: zap
  order: 4
---

`@vortex-api/convex-auth/tanstack-start` and `@vortex-api/convex-auth/tanstack-start/server` are the TanStack Start SSR adapter. Sessions live in app-origin HttpOnly cookies; a request middleware lands OAuth/magic-link redirects, refreshes sessions near expiry, and serves the intent-based `/api/auth` proxy. It implements the [SSR auth contract](/convex-auth/ssr-contract).

A working example lives in `examples/tanstack-start`.

## Install

```bash
pnpm add @vortex-api/convex-auth convex
```

Point the app at your Convex deployment and wire the backend (component mount, `convex/auth.ts`, `convex/http.ts`) per [Installation](/convex-auth/installation):

```bash
echo 'VITE_CONVEX_URL=https://<your-deployment>.convex.cloud' >> .env.local
```

## Request middleware

Register `convexAuthRequestMiddleware` in `src/start.ts`:

```ts
// src/start.ts
import { createStart } from "@tanstack/react-start";
import { convexAuthRequestMiddleware } from "@vortex-api/convex-auth/tanstack-start/server";
import { api } from "../convex/_generated/api";

export const startInstance = createStart(() => ({
  requestMiddleware: [convexAuthRequestMiddleware({ actions: api.auth })],
}));
```

The middleware intercepts auth requests and the `/api/auth` proxy internally — no separate route wiring is needed.

### Options

| Option                   | Default                                                     | Description                                                                                                                                                             |
| ------------------------ | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `actions`                | required                                                    | The `api.auth` object re-exporting `convexAuth` actions.                                                                                                                |
| `convexUrl`              | `CONVEX_URL` → `VITE_CONVEX_URL` → `NEXT_PUBLIC_CONVEX_URL` | Convex deployment URL.                                                                                                                                                  |
| `apiRoute`               | `/api/auth`                                                 | Path that proxies auth actions.                                                                                                                                         |
| `cookieConfig.maxAge`    | `null` (session cookie)                                     | Cookie lifetime in seconds. Must be `null` or a positive number.                                                                                                        |
| `requireLandingVerifier` | `true`                                                      | Require OAuth/magic-link landings to carry a verifier param matching the cookie minted at flow start. Set `false` only for deployments that predate verifier threading. |
| `verbose`                | `false`                                                     | Debug logging.                                                                                                                                                          |

## Server state + provider

Resolve auth state in the root route's `beforeLoad` via a server function, then pass it to `ConvexAuthTanstackStartProvider`:

```ts
// src/lib/auth-server.ts
import { createServerFn } from "@tanstack/react-start";
import { getRequest } from "@tanstack/react-start/server";
import {
  getAuthServerState,
  getConvexAuthSession,
} from "@vortex-api/convex-auth/tanstack-start/server";
import { api } from "../../convex/_generated/api";

/**
 * Server-resolved auth state for the root route's `beforeLoad` — seeds the
 * client provider and feeds route guards. Revocation-aware.
 */
export const getAuthState = createServerFn({ method: "GET" }).handler(async () => {
  return await getAuthServerState(getRequest(), { actions: api.auth });
});

/**
 * Verified session for protected loaders/server functions that don't want
 * the full middleware context.
 */
export const getSession = createServerFn({ method: "GET" }).handler(async () => {
  return await getConvexAuthSession(getRequest(), { actions: api.auth });
});
```

```tsx
// src/routes/__root.tsx
import { HeadContent, Outlet, Scripts, createRootRoute } from "@tanstack/react-router";
import { ConvexProvider, ConvexReactClient } from "convex/react";
import { ConvexAuthTanstackStartProvider } from "@vortex-api/convex-auth/tanstack-start";
import { api } from "../../convex/_generated/api";
import { getAuthState } from "../lib/auth-server";

const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);

export const Route = createRootRoute({
  beforeLoad: async () => ({
    auth: await getAuthState(),
  }),
  component: RootComponent,
});

function RootComponent() {
  const { auth } = Route.useRouteContext();

  return (
    <html lang="en">
      <body>
        <ConvexProvider client={convex}>
          <ConvexAuthTanstackStartProvider actions={api.auth} serverState={auth}>
            <Outlet />
          </ConvexAuthTanstackStartProvider>
        </ConvexProvider>
        <Scripts />
      </body>
    </html>
  );
}
```

## Server helpers

From `@vortex-api/convex-auth/tanstack-start/server`:

| Helper                                       | Returns                              | Notes                                                                               |
| -------------------------------------------- | ------------------------------------ | ----------------------------------------------------------------------------------- |
| `getAuthServerState(request, { actions })`   | `ConvexAuthServerState`              | Server-resolved state for the provider's `serverState` prop.                        |
| `getConvexAuthSession(request, { actions })` | `VerifiedSession`                    | Verified session — revocation-aware.                                                |
| `getConvexAuthToken(request)`                | `string \| null`                     | Raw session token.                                                                  |
| `convexAuthCookieState(request)`             | `{ hasSessionCookie, tokenExpired }` | Optimistic boundary check — revocation-blind, pre-filtering only.                   |
| `convexAuthProxyHandler(options)`            | `(request) => Promise<Response>`     | Factory — standalone proxy handler if you mount `/api/auth` outside the middleware. |

## Route guards are UX, not security

`beforeLoad` redirects and `convexAuthCookieState()` checks shape navigation; they are not authorization. Always verify the session at the data boundary (`getConvexAuthSession`, `verifySession`, or `ctx.auth.getUserIdentity()` inside Convex functions).

## Error params

Rejected landings redirect to `/?error=landing_verifier_mismatch` (verifier cookie absent or mismatched) or `/?error=cross_origin` (credentialed cross-origin request). Surface these in your UI — see the [SSR auth contract](/convex-auth/ssr-contract) for the full semantics.

## Sign-in UI

Client components use the React client — sign-in calls mint the session through the `/api/auth` proxy into HttpOnly cookies:

```tsx
// src/routes/sign-in.tsx
import { createFileRoute, useRouter } from "@tanstack/react-router";
import { FormEvent, useState } from "react";
import { useConvexAuthClient } from "@vortex-api/convex-auth/react";

export const Route = createFileRoute("/sign-in")({ component: SignInPage });

function SignInPage() {
  const authClient = useConvexAuthClient();
  const router = useRouter();

  const [error, setError] = useState<string | null>(null);

  const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const data = new FormData(e.currentTarget);
    const result = await authClient.signIn.email({
      email: String(data.get("email")),
      password: String(data.get("password")),
    });
    if (result.error) {
      setError(result.error.message ?? "Sign in failed");
      return;
    }
    // 2FA-enrolled accounts resolve without a session — show the TOTP step.
    if (result.data?.twoFactorRedirect) return;
    // Re-run beforeLoad/loaders so route context picks up the fresh session.
    await router.invalidate();
    await router.navigate({ to: "/dashboard" });
  };

  const onGithub = async () => {
    const result = await authClient.signIn.social({ provider: "github" });
    // social() returns the authorization URL — it does not navigate for you.
    if (result.data?.url) window.location.href = result.data.url;
  };

  return (
    <form onSubmit={onSubmit}>
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      <button type="submit">Sign in</button>
      <button type="button" onClick={onGithub}>
        Continue with GitHub
      </button>
      {error ? <p role="alert">{error}</p> : null}
    </form>
  );
}
```

The full form (sign-up, redirect search param, guest) is in `examples/tanstack-start/src/routes/sign-in.tsx`. See the [React guide](/convex-auth/react) for the complete hooks API.

## Example app

`examples/tanstack-start` is a full SSR app on the adapter — the root route resolves the verified session in `beforeLoad` and seeds `ConvexAuthTanstackStartProvider`, a pathless `_authed` layout guards navigation, and protected `createServerFn`s declare `convexAuthFunctionMiddleware`.

```bash
cd examples/tanstack-start
pnpm install
pnpm dlx convex dev   # local backend on :3212
pnpm run dev          # app on :3200
```

## Next steps

**[React hooks](/convex-auth/react)**

`useSession`, `useConvexAuthClient`, and the drop-in client.

**[SSR auth contract](/convex-auth/ssr-contract)**

The boundary semantics every SSR adapter implements.

**[Production](/convex-auth/production)**

Deploy, monitor, and rotate.

**[Example source](https://github.com/VortexNYC/convex-auth/tree/main/examples/tanstack-start)**

`examples/tanstack-start` on GitHub.
