remix-run/react-router · error · Error
SPA Mode: Received a ${response.status} status code from `en
Error message
SPA Mode: Received a ${response.status} status code from `entry.server.tsx` while prerendering your SPA Fallback HTML file.\n${html} What it means
Thrown in `postProcess` when `metadata.type === "spa"`. In SPA mode (`ssr:false`) React Router must render a single fallback HTML file (`/__spa-fallback.html`) that the server can serve for any non-prerendered path; if `entry.server.tsx` returns anything other than 200 for that fallback render, hydration cannot work. The full HTML body is included in the error to aid diagnosis.
Source
Thrown at packages/react-router-dev/vite/plugin.ts:2605
`\`entry.server.tsx\` while prerendering the \`${pathname}\` ` +
`path.\n${new TextDecoder().decode(contents)}`,
);
}
return [
{
path: pathname,
contents,
},
];
}
// Handle document responses (html or spa)
let html = await response.text();
if (metadata.type === "spa") {
if (response.status !== 200) {
throw new Error(
`SPA Mode: Received a ${response.status} status code from ` +
`\`entry.server.tsx\` while prerendering your SPA Fallback HTML file.\n` +
html,
);
}
if (
!html.includes("window.__reactRouterContext =") ||
!html.includes("window.__reactRouterRouteModules =")
) {
throw new Error(
"SPA Mode: Did you forget to include `<Scripts/>` in your root route? " +
"Your pre-rendered HTML cannot hydrate without `<Scripts />`.",
);
}
// SPA fallback is written to root regardless of basename
return [View on GitHub (pinned to 1fd704a7da)
Solutions
- Run `react-router serve`/preview and request `/` to see the actual SSR error in the terminal.
- Inspect `entry.server.tsx` for paths that throw or return non-200 responses unconditionally.
- Ensure the root route renders without depending on SSR-only data; move dynamic logic into loaders that run after hydration.
- Confirm `ssr:false` is intended — if you need SSR, remove the flag.
Example fix
// before: entry.server.tsx returns 500 in SPA mode
export default function handle(request, { loadContext }) {
return new Response("err", { status: 500 });
}
// after: delegate to the standard renderer
import { handleRequest } from "@react-router/node";
export default (request, loadContext) =>
handleRequest(request, loadContext, build); Defensive patterns
Strategy: try-catch
Validate before calling
const assertEntryServerHealthy = async (renderSpaFallback) => {
const res = await renderSpaFallback();
if (res.status !== 200) throw new Error(`entry.server returned ${res.status}`);
}; Try / catch
try {
await build(root, opts);
} catch (e) {
if (/SPA Mode: Received a/.test(String(e.message))) {
console.error(e.message); // includes the rendered HTML
}
throw e;
} Prevention
- In SPA mode, ensure the root route and `entry.server.tsx` render without SSR-only dependencies.
- Don't return non-200 responses from `entry.server.tsx` for the SPA fallback.
- Run a local preview to surface the underlying SSR error before pushing to CI.
When it happens
Trigger: `ssr:false` is set and the root render throws (e.g. an error boundary fires during SSR); `entry.server.tsx` returns a custom error `Response`; the root loader throws; `entry.server.tsx` is missing or exports a broken `handleRequest`.
Common situations: Switching to `ssr:false` while the root route depends on SSR-only APIs; an exception in `entry.server.tsx` that returns a 500; using a custom server entry that rejects in SPA mode; a broken root layout that throws during render.
Related errors
- Prerender (data): Received a ${response.status} status code
- Prerender (resource): Received a ${response.status} status c
- Prerender (html): Received a ${response.status} status code
- Invalid route exports found when prerendering with `ssr:fals
- SPA Mode: Did you forget to include `<Scripts/>` in your roo
AI-assisted analysis of remix-run/react-router@1fd704a7da (2026-08-12).
Data as JSON: /api/errors/60c8d24aecf70e90.
Report an issue: GitHub.