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

  1. 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)
  2. Verify the root returns a complete `<html><head/><body>…<Scripts/></body></html>` structure
  3. 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

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


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)