remix-run/react-router · error
SPA Mode: Received a
Error message
SPA Mode: Received a ${response.status} status code from `entry.server.tsx` while prerendering your SPA Fallback HTML file.
${html} What it means
With `ssr: false` (SPA Mode), React Router prerenders a fallback HTML shell by rendering `/` through your `entry.server.tsx`. That render must return 200; any other status means the SPA shell itself failed to render, and since the fallback is the only HTML a SPA has, the build aborts. The offending HTML is included in the message.
Solutions
- Check the HTML in the error message — it is usually the rendered error boundary showing the real exception
- Fix the throw in the root route / entry.server.tsx so `/` renders 200 in a build environment
- If a root loader exists in SPA Mode, either prerender `/` or move the logic to `clientLoader`
- Simplify by removing a custom entry.server.tsx so the framework default shell is used
Example fix
// before (app/root.tsx) — throws during SPA fallback render
export async function loader() {
throw new Error('DB unavailable at build time');
}
// after — no server loader in SPA Mode; load on the client
export async function clientLoader() {
return { user: await fetch('/api/me').then((r) => r.json()) };
} Defensive patterns
Strategy: validation
Validate before calling
// Verify the SPA fallback renders 200 before building
// (run `react-router-serve` or `vite dev` and request '/')
const res = await fetch('http://localhost:3000/');
if (res.status !== 200) throw new Error(`Root render returned ${res.status} — fix before SPA build`); Try / catch
try {
await build();
} catch (e) {
if (e instanceof Error && e.message.startsWith('SPA Mode: Received a')) {
// the message embeds the rendered error HTML — inspect it for the throw site
}
} Prevention
- In SPA Mode, avoid root `loader`s or ensure `/` is prerendered successfully
- Test that `/` renders without errors in dev before building with `ssr: false`
- Don't return non-200 responses from entry.server.tsx for the root path
When it happens
Trigger: `ssr: false` in react-router.config.ts combined with an entry.server render path that returns an error status (throwing in the root route's default export, a failing root loader without prerender, an ErrorBoundary rendering during the fallback request); a custom entry.server.tsx that inspects the request and responds non-200 for `/`.
Common situations: Enabling SPA Mode on an app whose root loader throws at build time; entry.server.tsx copied from an SSR setup that returns `new Response(..., { status: 500 })` on render errors without handling the SPA fallback; root ErrorBoundary triggering because a context provider reads server-only data.
Related errors
- Invalid route exports found when prerendering with…
- SPA Mode: Did you forget to include `<Scripts/>` in your…
- Could not find package.json in
- ⚠️ Paths with dynamic/splat params cannot be prerendered…
- Prerender (data): Received a
AI-assisted analysis of remix-run/react-router@6beaca3952 (2026-08-18).
Data as JSON: /api/errors/cccf0aab1b2b104a.
Report an issue: GitHub.
Appendix: 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 6beaca3952)