Skip to content

TanStack Start

TanStack Start runs loaders and server functions on the server, so their fetch calls go through the proxy without a browser context — the same situation as Next.js SSR. The proxy identifies which session those requests belong to via the x-test-rcrd-id header. Playwright’s playwrightProxy.before() already sets it on the browser navigation that triggers SSR, so the id arrives on the incoming server request — the job is to attach it to outgoing server-side requests. (Browser-only tests need none of this; the proxy falls back to the globally set session.)

One line in your router setup tags every server-side fetch — route loaders, server functions, and server routes:

src/router.tsx
import { registerProxyFetch } from 'test-proxy-recorder/tanstack-start';
registerProxyFetch(); // no-op on the client / in production unless TEST_PROXY_RECORDER_ENABLED=true

It patches the global fetch to copy the current request’s x-test-rcrd-id onto outgoing requests, reading it from TanStack Start’s server request context (getRequestHeader). Put it at the top of src/router.tsx — that module runs on the server for every SSR request, and the call is idempotent, a no-op on the client, and a no-op in production unless the recorder is explicitly enabled.

Patch-free. Use it for a single fetch inside a loader or server function, or when you’d rather not patch global fetch:

import { createHeadersWithRecordingId } from 'test-proxy-recorder/tanstack-start';
const res = await fetch('http://localhost:8100/todos', {
headers: await createHeadersWithRecordingId({ 'Content-Type': 'application/json' }),
});

getRecordingId() is also exported if you want the raw id (or null) to forward yourself. Both read the current request’s id from the server context, and both no-op in production unless TEST_PROXY_RECORDER_ENABLED=true.

In dev/test, point your backend base URLs at the proxy so both origins are recorded — the server-side base (read by loaders/server functions, e.g. BACKEND_URL) and the browser-side base baked in at build (VITE_API_URL). In production, point them at the real backend. Browser-side requests are handled by playwrightProxy.before()’s HAR mechanism, exactly as in the manual setup.

The recorder works with your real auth provider (AWS Cognito, Auth0, Clerk, …), and it composes with the SSR tagging above. The pattern:

  • Log in for real, in transparent mode. A Playwright setup project signs in once with the proxy passed through, so the login is never recorded, and saves the session (storageState) that the authenticated specs reuse.
  • Protected requests carry the token and are recorded. Each authenticated request sends an Authorization: Bearer … header; the recorder redacts it, so no token reaches the committed recordings.
  • Where the token lives decides the mechanism. A token in localStorage can’t be read on the server, so the protected fetch runs in the browser and is recorded via HAR — no SSR prefetch. A cookie-based session, by contrast, can be forwarded into a loader with createHeadersWithRecordingId() and recorded server-side.

The example-tanstack-start app includes a runnable /login/dashboard AWS Cognito flow (e2e/setup-auth.ts + e2e/auth.spec.ts) demonstrating exactly this.

A complete, runnable app — built with TanStack Query (SSR prefetch + useMutation), covering todos (browser + SSR), a cache-header ISR route, a redaction case, WebSocket chat, and a real AWS Cognito login (transparent-mode auth + a recorded, token-redacted protected API), all recorded and replayed — lives in apps/example-tanstack-start. It shows the recorder is transparent to your data layer: registerProxyFetch() tags Query’s queryFn fetches during SSR with no Query-specific code.