remix-run/react-router · error · Error

React Router Vite plugin can't detect preamble. Something is

Error message

React Router Vite plugin can't detect preamble. Something is wrong.

What it means

Thrown at runtime inside the React Refresh preamble injected by the plugin (`REACT_REFRESH_HEADER`). Each HMR-affected module is prefixed with this snippet so that, on hot update, components self-register with Vite's Refresh runtime. If `window.__vite_plugin_react_preamble_installed__` is not set, the preamble that bootstraps `$RefreshReg$`/`$RefreshSig$` was never executed for the page, and component registration would silently fail — so the plugin throws to surface the misconfiguration.

Source

Thrown at packages/react-router-dev/vite/plugin.ts:2802

  return (
    REACT_REFRESH_HEADER.replaceAll("__SOURCE__", JSON.stringify(id)) +
    code +
    REACT_REFRESH_FOOTER.replaceAll("__SOURCE__", JSON.stringify(id))
      .replaceAll("__ACCEPT_EXPORTS__", JSON.stringify(acceptExports))
      .replaceAll("__ROUTE_ID__", JSON.stringify(route?.id))
  );
}

const REACT_REFRESH_HEADER = `
import RefreshRuntime from "${virtualHmrRuntime.id}";

const inWebWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
let prevRefreshReg;
let prevRefreshSig;

if (import.meta.hot && !inWebWorker) {
  if (!window.__vite_plugin_react_preamble_installed__) {
    throw new Error(
      "React Router Vite plugin can't detect preamble. Something is wrong."
    );
  }

  prevRefreshReg = window.$RefreshReg$;
  prevRefreshSig = window.$RefreshSig$;
  window.$RefreshReg$ = (type, id) => {
    RefreshRuntime.register(type, __SOURCE__ + " " + id)
  };
  window.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
}`.replaceAll("\n", ""); // Header is all on one line so source maps aren't affected

const REACT_REFRESH_FOOTER = `
if (import.meta.hot && !inWebWorker) {
  window.$RefreshReg$ = prevRefreshReg;
  window.$RefreshSig$ = prevRefreshSig;
  RefreshRuntime.__hmr_import(import.meta.url).then((currentExports) => {
    RefreshRuntime.registerExportsForReactRefresh(__SOURCE__, currentExports);

View on GitHub (pinned to 1fd704a7da)

Solutions

  1. Restore `<Scripts />` in your root route's `<body>`.
  2. Ensure `@vitejs/plugin-react` (or the Refresh plugin) is registered and ordered before `reactRouter()`.
  3. Loosen CSP or nonces so the inline preamble can execute during dev.
  4. Avoid double React plugin registration.

Example fix

// before: root omits Scripts in dev
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

import { readFileSync } from "node:fs";
const rootSrc = readFileSync("app/root.tsx", "utf8");
if (!/<Scripts\s*\/?\>/.test(rootSrc)) {
  throw new Error("<Scripts /> missing — React Refresh preamble won't load");
}

Prevention

When it happens

Trigger: The HTML shell does not include the React Refresh preamble script (normally injected by `@vitejs/plugin-react`/`@vitejs/plugin-react-refresh`); a custom `entry.server.tsx` or root route that omits `<Scripts />` in dev; the React Refresh preamble was stripped by an HTML minifier or CSP; multiple conflicting React plugins loaded.

Common situations: Removing `<Scripts />` in dev to "clean up" the document; adding a CSP that blocks the inline preamble; loading React Router's dev runtime in an iframe/WebView that lacks the preamble; conflict between `@vitejs/plugin-react` and a manual Refresh setup.

Related errors


AI-assisted analysis of remix-run/react-router@1fd704a7da (2026-08-12). Data as JSON: /api/errors/d8f0b47c701da2b1. Report an issue: GitHub.