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
- Restore `<Scripts />` in your root route's `<body>`.
- Ensure `@vitejs/plugin-react` (or the Refresh plugin) is registered and ordered before `reactRouter()`.
- Loosen CSP or nonces so the inline preamble can execute during dev.
- 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
- Always include `<Scripts />` in dev so the Refresh preamble executes.
- Register `@vitejs/plugin-react` before `reactRouter()` in `plugins`.
- Don't let CSP block the inline preamble during dev.
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.