effect-auth

TanStack Start

Proxy a private effect-auth Worker through a same-origin TanStack Start route.

The canonical Cloudflare topology keeps the browser same-origin while TanStack Start forwards /auth/* to a private auth Worker through a service binding.

TanStack Start keeps the browser contract same-origin while auth infrastructure remains private

This guide hardens the maintained tanstack-cloudflare-auth-split shape by setting url: false. The current example still exports an auth backend URL, so add this setting before treating it as private. TanStack APIs evolve; the repository tests its pinned 1.168.x dependency graph.

FileResponsibility
alchemy.run.tsD1, Durable Object, Workers, bindings
src/routes/auth/$.tsTransparent same-origin proxy
src/auth/client.tsBrowser client and session query
src/server/auth.tsEffect Layers and cached web handler
src/workers/auth-backend.tsPrivate Worker entry and DO export

Declare the topology

alchemy.run.ts
import * as Cloudflare from "alchemy/Cloudflare";
import * as Config from "effect/Config";

const AuthBackend = Cloudflare.Worker("AuthBackend", {
  main: "./src/workers/auth-backend.ts",
  url: false,
  compatibility: { date: "2026-03-17", flags: ["nodejs_compat"] },
  env: {
    DB: Database,
    RATE_LIMITER: RateLimiter,
    AUTH_SECRET: Config.redacted("AUTH_SECRET"),
    AUTH_PUBLIC_URL: Config.string("AUTH_PUBLIC_URL"),
  },
});

const Website = Cloudflare.Website.Vite("Website", {
  compatibility: { date: "2026-03-17", flags: ["nodejs_compat"] },
  env: { AUTH_BACKEND: AuthBackend },
});

url: false keeps auth off the public Internet. Bind D1, limiter, email, and secrets only to that Worker. Use a reviewed compatibility date supported by the pinned Alchemy release.

Forward the original request

src/routes/auth/$.ts
import { createFileRoute } from "@tanstack/react-router";
import { env } from "../../server/env";

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

Do not parse/re-encode bodies, reconstruct Request, copy headers into plain objects, or manufacture a new Response. Transparent forwarding preserves Cookie, Origin, streaming, redirects, and every Set-Cookie header.

Share the browser session query

src/auth/client.ts
import {
  createAuthClient,
  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,
  });

After sign-in, invalidate authSessionQueryKey; after logout, set it to null for immediate UI feedback. Never persist the HTTP-only session token in application state.

SSR and authorization

beforeLoad can run on server render and client navigation. Server-side guards must resolve the session from request-aware logic carrying the incoming cookie; a relative browser fetch does not automatically inherit the SSR request cookie. Either provide that session through router context or render a neutral shell until hydration.

BoundaryWhat it protects
React/session queryAccount controls and UI state
Router beforeLoadNavigation/rendering
Server route/operation authorizationPrivate data and mutations

UI and router gates are not backend authorization. Independently authenticate and authorize every server operation.

Most Start apps use the default server entry. Add a custom entry only to export the rate-limit Durable Object or customize fetch handling; follow the current TanStack src/server.ts guidance when upgrading rather than copying a version-specific entry blindly.

Run local development through Alchemy so service bindings exist. Test the public website URL, session cookies, redirects, hostile origins, backend-binding failure, and multiple Set-Cookie propagation. See Split Frontend and Auth Workers and Cloudflare Workers.

On this page