---
title: "TanStack Start"
url: "https://effect-auth.itsbroly.com/frameworks/tanstack-start/"
description: "Integrate effect-auth with a TanStack Start application."
---



TanStack Start can expose effect-auth through a native server route while the React application uses the typed browser client. The simplest deployment puts Start, CoreAuthHttpApi, and storage in one Worker. Requests stay same-origin, so the default client path and session cookie work without CORS configuration.

This guide follows the maintained [`tanstack-cloudflare-auth`](https://github.com/nr1brolyfan/effect-auth/tree/main/examples/tanstack-cloudflare-auth) example. Start's APIs are still evolving; compare upgrades with the official [Getting Started](https://tanstack.com/start/latest/docs/framework/react/getting-started), [Server Routes](https://tanstack.com/start/latest/docs/framework/react/guide/server-routes), [Server Entry Point](https://tanstack.com/start/latest/docs/framework/react/guide/server-entry-point), and [Cloudflare Workers](https://tanstack.com/start/latest/docs/framework/react/guide/hosting#cloudflare-workers--official-partner) documentation. The snippets below intentionally use server routes, not older `createServerFn` wiring.

## File map [#file-map]

```text
src/
  auth-client.ts          # typed client and session query
  routes/auth/$.ts        # GET/POST /auth/* server route
  routes/index.tsx        # sign-in UI and session rendering
  server/auth.ts          # effect-auth HttpApp and runtime layers
  server-entry.ts         # optional Worker entry and Durable Object export
  router.tsx              # Router + React Query SSR integration
vite.config.ts            # Start plugin; custom entry only when required
```

First complete the [Quick Start](/quick-start/) to build `handleAuthRequest(request)`. That function should run the effect-auth `HttpApp` with your database, cookie, mail, and rate-limit layers and return a standard `Response`. The framework-specific boundary is deliberately small.

## Mount CoreAuthHttpApi [#mount-coreauthhttpapi]

Create a splat server route. The file name and route ID are significant: `$` captures everything below `/auth/`, including `/auth/session` and password endpoints.

```ts title="src/routes/auth/$.ts"
import { createFileRoute } from "@tanstack/react-router";

import { handleAuthRequest } from "../../server/auth";

export const Route = createFileRoute("/auth/$")({
  server: {
    handlers: {
      GET: ({ request }) => handleAuthRequest(request),
      POST: ({ request }) => handleAuthRequest(request),
    },
  },
});
```

Start passes the original Web `Request` to the handler and accepts the Web `Response` returned by effect-auth. Do not parse or reconstruct the request: auth bodies, origin headers, redirects, and `Set-Cookie` must pass through intact. Add another method only when an enabled endpoint actually requires it.

## Create the client and session query [#create-the-client-and-session-query]

With `/auth/*` on the same origin, `createAuthClient()` needs no base URL. A React Query option centralizes the session contract and cancellation behavior:

```ts title="src/auth-client.ts"
import { createAuthClient } from "@effect-auth/core/Client";
import type { CurrentSessionResponse } from "@effect-auth/core/Client";
import { queryOptions } from "@tanstack/react-query";

export const authClient = createAuthClient();
export const authSessionQueryKey = ["auth", "session"] as const;

export const currentSessionQueryOptions = () =>
  queryOptions({
    queryKey: authSessionQueryKey,
    queryFn: async ({ signal }): Promise<CurrentSessionResponse | null> =>
      (await authClient.session.currentOrUndefined({ signal })) ?? null,
  });
```

See [Browser Client](/clients/browser-client/) for available operations. In a component, call `useQuery(currentSessionQueryOptions())`. After successful sign-in, invalidate `authSessionQueryKey`; after logout, setting that query to `null` gives immediate UI feedback. The API commits the HTTP-only session cookie, so application code should not store session tokens.

```tsx
const sessionQuery = useQuery(currentSessionQueryOptions());
const session = sessionQuery.data ?? null;

const signIn = useMutation({
  mutationFn: (input: { email: string; password: string }) =>
    authClient.password.signIn(input),
  onSuccess: () =>
    queryClient.invalidateQueries({ queryKey: authSessionQueryKey }),
});
```

The maintained example creates one `QueryClient` per router and calls `setupRouterSsrQueryIntegration({ router, queryClient })`. This allows React Query state used during Start rendering to dehydrate and hydrate consistently. A component session query is useful for rendering account controls, but it is not an authorization boundary.

## SSR and protected routes [#ssr-and-protected-routes]

TanStack Router's `beforeLoad` may run during the initial server render and during client navigation. If you protect a route there, resolve the session through request-aware server logic (including the incoming `Cookie` header), return it in router context, and throw `redirect()` when absent. Do not make an SSR guard depend on browser-only state or assume a relative browser fetch automatically carries the server render's request cookie.

On client navigation, the session query can drive UI gating because same-origin requests include the cookie. On the initial request, either render a neutral/loading shell until hydration or supply a server-resolved session to router context. In both cases, independently authorize every server route or backend operation that returns private data. `beforeLoad` protects navigation and rendered UI; users can call an endpoint without visiting that route. See TanStack Router's [Authenticated Routes](https://tanstack.com/router/latest/docs/guide/authenticated-routes) warning and patterns.

## Cloudflare entry [#cloudflare-entry]

Start provides a default server entry, so most applications do not need one. The maintained same-worker example configures `tanstackStart({ server: { entry: "server-entry" } })` because its entry exports effect-auth's rate-limit Durable Object alongside Start's fetch handler. Keep that custom entry only if you need Worker exports or fetch customization; otherwise follow Start's current default and Cloudflare adapter documentation rather than copying infrastructure-specific code.

## Split backend [#split-backend]

The maintained [`tanstack-cloudflare-auth-split`](https://github.com/nr1brolyfan/effect-auth/tree/main/examples/tanstack-cloudflare-auth-split) example deploys Start and CoreAuthHttpApi as separate Workers connected by a Cloudflare service binding. The browser still calls same-origin `/auth/*`; the Start route forwards the untouched request:

```ts
export const Route = createFileRoute("/auth/$")({
  server: {
    handlers: {
      GET: ({ request }) => env.AUTH_BACKEND.fetch(request),
      POST: ({ request }) => env.AUTH_BACKEND.fetch(request),
    },
  },
});
```

This preserves the simple client and cookie model while isolating auth resources. The split example also proxies `/api/$` to the backend for protected application APIs. Prefer the same-worker topology until independent deployment, bindings, or security boundaries justify the extra Worker; direct cross-origin browser calls require deliberate cookie, origin, and CORS configuration and are not the canonical setup here.

