remix-run/react-router · error · Error
SPA Mode: Did you forget to include `<Scripts/>` in your roo
Error message
SPA Mode: Did you forget to include `<Scripts/>` in your root route? Your pre-rendered HTML cannot hydrate without `<Scripts />`.
What it means
Thrown in `postProcess` when `metadata.type === "spa"` and the status is 200 but the rendered HTML is missing the inline `<Scripts/>` output. React Router detects SPA-readiness by checking for `window.__reactRouterContext =` and `window.__reactRouterRouteModules =` in the rendered HTML; their absence means `<Scripts/>` (and therefore the bootstrap payload) is not in the root route, so the SPA cannot hydrate.
Source
Thrown at packages/react-router-dev/vite/plugin.ts:2616
}
// 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 [
{
path: "/__spa-fallback.html",
contents: html,
},
];
}
// Handle html responses
let pathname = new URL(request.url).pathname;
if (redirectStatusCodes.has(response.status)) {View on GitHub (pinned to 1fd704a7da)
Solutions
- Open the root route (`app/root.tsx`) and confirm `<Scripts />` is rendered inside `<body>` (typically alongside `<Scripts />`, `<ScrollRestoration />`).
- Ensure `<Scripts />` is unconditional in the document layout.
- If using a custom `entry.server.tsx`, do not strip `<script>` tags from the rendered HTML.
- Rebuild and grep the produced `__spa-fallback.html` for `__reactRouterContext`.
Example fix
// before: root.tsx omits Scripts
export default function Root() {
return (<html><body><Outlet/></body></html>);
}
// after
import { Scripts, Outlet } from "react-router";
export default function Root() {
return (
<html><body><Outlet/><Scripts/></body></html>
);
} Defensive patterns
Strategy: validation
Validate before calling
// Statically assert root renders Scripts
import { readFileSync } from "node:fs";
const rootSrc = readFileSync("app/root.tsx", "utf8");
if (!/<Scripts\s*\/?\>/.test(rootSrc)) {
throw new Error("app/root.tsx must render <Scripts />");
} Prevention
- Always render `<Scripts />` in the root document body for SPA mode.
- Make `<Scripts />` unconditional (no env-gating).
- Add a lint rule or pre-build grep for `<Scripts />` in `app/root.tsx`.
When it happens
Trigger: Root route's `<html>` document does not render `<Scripts />`; `<Scripts />` is conditionally rendered and skipped in the fallback render; a custom `entry.server.tsx` strips script tags; the root layout was edited to remove `<Scripts />` while experimenting.
Common situations: Customizing the root document and forgetting `<Scripts />`; copy-pasting an HTML template from a non-React-Router source; conditionally hiding `<Scripts />` based on env vars that evaluate differently at build time.
Related errors
- SPA Mode: Received a ${response.status} status code from `en
- Prerender (data): Received a ${response.status} status code
- React Router Vite plugin can't detect preamble. Something is
- Invalid route exports found when prerendering with `ssr:fals
- Could not find a root route module in the app directory: ${a
AI-assisted analysis of remix-run/react-router@1fd704a7da (2026-08-12).
Data as JSON: /api/errors/f17aedd98e97bea3.
Report an issue: GitHub.