remix-run/react-router · error
SPA Mode: Did you forget to include `<Scripts/>` in your…
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
In SPA Mode the prerendered fallback HTML must contain the `window.__reactRouterContext` and `window.__reactRouterRouteModules` assignments, which `<Scripts />` from `react-router` emits. If either marker is missing, hydration in the browser would silently fail, so the build fails fast with a pointer to the missing component. Custom root layouts or error documents that omit `<Scripts />` are the usual cause.
Solutions
- Import `Scripts` from `react-router` and render it inside `<body>` in the root route's default export (and any custom ErrorBoundary that renders a full document)
- Verify the root returns a complete `<html><head/><body>…<Scripts/></body></html>` structure
- Remove custom entry.server.tsx transformations that drop script tags
Example fix
// before (app/root.tsx)
import { Outlet } from 'react-router';
export default function Root() {
return (
<html>
<head />
<body>
<Outlet />
</body>
</html>
);
}
// after
import { Outlet, Scripts } from 'react-router';
export default function Root() {
return (
<html>
<head />
<body>
<Outlet />
<Scripts />
</body>
</html>
);
} Defensive patterns
Strategy: validation
Validate before calling
// Assert the root document includes Scripts before SPA build
import Root from './app/root';
// simplest guard: grep the source
const src = fs.readFileSync('app/root.tsx', 'utf8');
if (!/\<Scripts\s*\/?\>/.test(src)) {
throw new Error('Root route must render <Scripts /> for SPA Mode');
} Try / catch
try {
await build();
} catch (e) {
if (e instanceof Error && e.message.includes('<Scripts/>')) {
console.error('Add <Scripts /> from react-router to app/root.tsx');
process.exit(1);
}
throw e;
} Prevention
- Always render `<Scripts />` (and `<ScrollRestoration />` if used) inside `<body>` of the root route
- Include `<Scripts />` in any custom root ErrorBoundary that renders a full document
- Start SPA projects from the official SPA template
When it happens
Trigger: `ssr: false` with a root route whose HTML shell does not render `<Scripts />` (e.g. hand-written `<html>` without it); `<Scripts />` commented out to avoid a flash; a custom entry.server.tsx that strips scripts from the output; conditional rendering that excludes `<Scripts />` on the server pass.
Common situations: Converting an existing React SPA to React Router SPA Mode and hand-rolling the document; removing `<Scripts />` while debugging; early-returns in the Root component for loading states that skip the document structure.
Related errors
- Invalid route exports found when prerendering with…
- SPA Mode: Received a
- Invalid payload type
- No result found for routeId
- You must be using the SSR features of React Router in order…
AI-assisted analysis of remix-run/react-router@6beaca3952 (2026-08-18).
Data as JSON: /api/errors/f17aedd98e97bea3.
Report an issue: GitHub.
Appendix: 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 6beaca3952)