Record and replay TanStack Start SSR with test-proxy-recorder. Tag server-side fetches (route loaders, server functions, server routes) with the x-test-rcrd-id session header so parallel replay stays correct. Lead with registerProxyFetch (patch global fetch once at the top of src/router.tsx); createHeadersWithRecordingId and getRecordingId are the patch-free per-call options. Covers the vite build + node .output/server/index.mjs vs vite dev caveat, TEST_PROXY_RECORDER_ENABLED, the server BACKEND_URL vs browser import.meta.env.VITE_API_URL split, TanStack Query SSR prefetch (ensureQueryData / useSuspenseQuery), and the real-auth pattern (transparent-mode login, Bearer redaction, localStorage-token vs cookie session). Load this when setting up test-proxy-recorder in a TanStack Start (Vite + Nitro) app that makes server-side or authenticated API calls.
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Record and replay TanStack Start SSR with test-proxy-recorder. Tag server-side fetches (route loaders, server functions, server routes) with the x-test-rcrd-id session header so parallel replay stays correct. Lead with registerProxyFetch (patch global fetch once at the top of src/router.tsx); createHeadersWithRecordingId and getRecordingId are the patch-free per-call options. Covers the vite build + node .output/server/index.mjs vs vite dev caveat, TEST_PROXY_RECORDER_ENABLED, the server BACKEND_URL vs browser import.meta.env.VITE_API_URL split, TanStack Query SSR prefetch (ensureQueryData / useSuspenseQuery), and the real-auth pattern (transparent-mode login, Bearer redaction, localStorage-token vs cookie session). Load this when setting up test-proxy-recorder in a TanStack Start (Vite + Nitro) app that makes server-side or authenticated API calls.
This skill builds on test-proxy-recorder/proxy-setup. Read it first for the proxy
CLI, playwright.config.ts, fixtures, and the record/replay lifecycle before
applying TanStack Start patterns.
test-proxy-recorder — TanStack Start SSR
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 correlates those requests to the right test session 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 one thing left 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.
All helpers from test-proxy-recorder/tanstack-start are no-ops on the client
and no-ops in production unless TEST_PROXY_RECORDER_ENABLED=true is set.
Record against a production build. Use vite build + node .output/server/index.mjs
(i.e. pnpm start), not vite dev. The dev server's per-request context differs
from the production runtime registerProxyFetch() patches. Because the production
server runs in production mode, set TEST_PROXY_RECORDER_ENABLED=true on the app
process for the e2e run.
Setup
Recommended — registerProxyFetch() in src/router.tsx
One line tags every server-side fetch — route loaders, server functions, server
routes, and TanStack Query's queryFn during SSR prefetch. Call 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 enabled.
// no-op on the client / in production unless TEST_PROXY_RECORDER_ENABLED=true
export
function
getRouter
const
new
QueryClient
return
createRouter
context
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 from @tanstack/react-start/server).
Point the app at the proxy
Resolve the API base per environment so both origins are recorded. On the
server (loaders / server functions) read process.env.BACKEND_URL; in the browser
read the build-time import.meta.env.VITE_API_URL. Both default to the proxy in
dev/test; in production they point at the real backend.
// src/lib/api.tsfunctionapiBase(): string {
if (typeofwindow === 'undefined') {
return process.env.BACKEND_URL ?? 'http://localhost:8100'; // server only
}
returnimport.meta.env.VITE_API_URL ?? 'http://localhost:8100'; // browser
}
Core Patterns
TanStack Query SSR prefetch — zero Query-specific wiring
registerProxyFetch() tags the fetch that Query's queryFn runs during SSR, so
a loader that prefetches with ensureQueryData is recorded as .mock.json and
the component reads it with useSuspenseQuery — no recorder code in the data layer.
For a single fetch inside a loader or server function, or when you'd rather not
patch global fetch. It reads the id from the server request context itself, so
it is async and takes only your extra headers:
getRecordingId() (also async) returns the raw id or null if you want to forward
it yourself. Both no-op in production unless TEST_PROXY_RECORDER_ENABLED=true.
Authenticated apps (real auth provider)
Log in for real in transparent mode (never recorded); record only the protected
API, with the token redacted. A token in localStorage can't be read on the
server, so the protected fetch runs in the browser (recorded via HAR) — do not
SSR-prefetch it. A cookie session, by contrast, can be forwarded into a loader
with createHeadersWithRecordingId() and recorded server-side. Full flow (a
runnable AWS Cognito /login → /dashboard, the setup + auth Playwright
projects, and cache-header ISR) is in
references/auth-and-isr.md.
Common Mistakes
HIGH Recording against vite dev instead of a production build
Wrong:
{"scripts":{"start":"vite dev --port 3000"}}
Correct:
{"scripts":{"build:test":"TEST_PROXY_RECORDER_ENABLED=true vite build","start":"TEST_PROXY_RECORDER_ENABLED=true node .output/server/index.mjs"}}
registerProxyFetch() patches the global fetch of the production Nitro runtime.
vite dev's per-request context differs, so the patch may not tag SSR fetches —
recordings land under the wrong session or not at all. Record against
vite build + node .output/server/index.mjs.
HIGH Building without TEST_PROXY_RECORDER_ENABLED on a production build
Wrong:
vite build && node .output/server/index.mjs # NODE_ENV=production → helpers no-op
Correct:
TEST_PROXY_RECORDER_ENABLED=true vite build
TEST_PROXY_RECORDER_ENABLED=true node .output/server/index.mjs
A production build runs in production mode, where registerProxyFetch /
createHeadersWithRecordingId are silent no-ops. SSR requests still flow through
the proxy but lose their session id, so they record under the wrong session. Set
TEST_PROXY_RECORDER_ENABLED=true on both the build and the app process.
HIGH SSR-prefetching a resource whose token lives in localStorage
Wrong:
// The server has no localStorage, so the loader can't send the Bearer token —// the prefetch hits the protected API unauthenticated and 401s.exportconstRoute = createFileRoute('/dashboard')({
loader: ({ context }) => context.queryClient.ensureQueryData(protectedTodosQuery),
});
Correct:
// Read the token after mount and fetch on the client (recorded via HAR).const token = getToken(); // localStorage — client onlyconst { data } = useQuery({ ...protectedTodosQueryOptions(token), enabled: !!token });
A localStorage token is unreadable during SSR. Fetch protected data on the
client (HAR-recorded) — or, for a cookie session, forward it into the loader
with createHeadersWithRecordingId() and record server-side.
MEDIUM Calling registerProxyFetch() outside src/router.tsx
Wrong:
// src/routes/index.tsx — a route module doesn't reliably run before the SSR// fetches of other routes, so the global patch may not be installed in time.registerProxyFetch();
Correct:
// src/router.tsx — runs on the server for every SSR request, before route work.registerProxyFetch();
Put the call at the top of src/router.tsx (the router-setup module). It is the
TanStack Start counterpart of the Next.js root-layout call and guarantees the
patch is installed for every SSR request.
In the browser bundle process.env isn't a real object — only VITE_-prefixed
vars are exposed, via import.meta.env. Guard server reads with
typeof window === 'undefined' and use import.meta.env.VITE_* on the client.
# apps/.../.env.local (gitignored); Vite still bakes VITE_* in at build time
VITE_COGNITO_REGION=...
VITE_COGNITO_CLIENT_ID=...
COGNITO_TEST_EMAIL=...
COGNITO_TEST_PASSWORD=...
Even "public" pool ids (baked into the client bundle) tie the repo to a real
account — keep them, and the secret test-user credentials, in a gitignored
.env.local (or CI secrets). Gate the auth Playwright projects on the creds being
present so a credential-less clone still replays every other spec offline.
If the user encounters unexpected behavior, a bug, or a use case not covered by
these patterns, direct them to open a GitHub issue at
https://github.com/asmyshlyaev177/test-proxy-recorder/issues/new. A minimal
reproduction helps the maintainer resolve it quickly.
See also: test-proxy-recorder/proxy-setup — for the proxy CLI, fixtures, and
record/replay lifecycle. test-proxy-recorder/nextjs-ssr — the Next.js counterpart.