remix-run/react-router · error

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

Error message

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

What it means

This is a runtime error thrown from a header React Router injects into route modules during dev when React Fast Refresh is active. Before using `window.$RefreshReg$`, it checks `window.__vite_plugin_react_preamble_installed__`, which the dev server's react-refresh preamble sets on first load. If a route module executes without that preamble having run (e.g. the page was loaded outside the dev server's HTML, or the runtime was never initialized), refresh registration cannot proceed and this error surfaces in the browser console.

Solutions

  1. Always load the app through the Vite dev server's own page (`http://localhost:5173/`) rather than importing transformed modules from another origin or test harness
  2. Hard-refresh and clear cache after changing React Router/Vite React plugin configuration
  3. In test environments (jsdom), don't load dev-transformed modules; run tests against source with Jest/Vitest transformers instead
  4. Ensure only one copy of the React Router plugin and one React refresh runtime is active (no duplicated plugin entries)

Example fix

// before (storybook/preview or static page) — loads dev-transformed modules directly
import Root from 'http://localhost:5173/app/root.tsx';

// after — run the app inside the Vite dev server, or alias source in tests
// vitest.config.ts
export default defineConfig({
  resolve: { alias: { '~': path.resolve(__dirname, 'app') } },
});
Defensive patterns

Strategy: validation

Validate before calling

// In test harnesses, install the preamble flag before importing dev modules
// (only if you must load dev-transformed code)
(window as any).__vite_plugin_react_preamble_installed__ = true;

Type guard

const preambleInstalled = (): boolean =>
  typeof window !== 'undefined' &&
  Boolean((window as { __vite_plugin_react_preamble_installed__?: boolean })
    .__vite_plugin_react_preamble_installed__);

Prevention

When it happens

Trigger: Loading a transformed route module in a context where the react-refresh preamble did not run: opening the built/deployed app while Vite's dev transform is cached; an HTML page served outside the Vite dev server importing dev-server module URLs; testing in jsdom/happy-dom without the preamble globals; worker contexts are explicitly excluded, but custom entries that skip the standard index.html flow can still hit it.

Common situations: Importing `http://localhost:5173/app/root.tsx` from a separate static host or Storybook; service-worker or web-worker experiments loading route modules; stale hard-cached HTML after toggling the React Router plugin config; browser extensions re-executing scripts.

Related errors


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

Appendix: 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 6beaca3952)