remix-run/react-router · error

You must be using the SSR features of React Router in order

Error message

You must be using the SSR features of React Router in order to skip passing a `router` prop to `<RouterProvider>`

What it means

The framework-mode <RouterProvider> (hydrated-router) builds its router from window.__reactRouterContext, __reactRouterManifest, and __reactRouterRouteModules injected by the SSR build. initSsrInfo() finds none of them, so createHydratedRouter throws (lib/dom-export/hydrated-router.tsx89) when you omit the router prop outside that pipeline.

Source

Thrown at packages/react-router/lib/dom-export/hydrated-router.tsx:89

      routeModules: window.__reactRouterRouteModules,
      stateDecodingPromise: undefined,
      router: undefined,
      routerInitialized: false,
    };
  }
}

function createHydratedRouter({
  getContext,
  instrumentations,
}: {
  getContext?: RouterInit["getContext"];
  instrumentations?: ClientInstrumentation[];
}): DataRouter {
  initSsrInfo();

  if (!ssrInfo) {
    throw new Error(
      "You must be using the SSR features of React Router in order to skip " +
        "passing a `router` prop to `<RouterProvider>`",
    );
  }

  // We need to suspend until the initial state snapshot is decoded into
  // window.__reactRouterContext.state

  let localSsrInfo = ssrInfo;
  // Note: `stateDecodingPromise` is not coupled to `router` - we'll reach this
  // code potentially many times waiting for our state to arrive, but we'll
  // then only get past here and create the `router` one time
  if (!ssrInfo.stateDecodingPromise) {
    let stream = ssrInfo.context.stream;
    invariant(stream, "No stream found for single fetch decoding");
    ssrInfo.context.stream = undefined;
    ssrInfo.stateDecodingPromise = decodeViaTurboStream(stream, window)
      .then((value) => {

View on GitHub (pinned to 7aea711dd1)

Solutions

  1. Pass your own router: <RouterProvider router={createBrowserRouter(routes)} />
  2. Or stay in Framework Mode SSR so the build injects the hydration globals
  3. Do not reuse the generated entry.client.tsx in a non-framework setup; write your own root.tsx/entry

Example fix

// before
hydrateRoot(el, <RouterProvider />) // no SSR globals present
// after
const router = createBrowserRouter(routes)
hydrateRoot(el, <RouterProvider router={router} />)
Defensive patterns

Strategy: validation

Validate before calling

// Only use the hydrated RouterProvider when the SSR build injected its globals
const hasFrameworkGlobals =
  typeof window !== 'undefined' &&
  Boolean(window.__reactRouterContext && window.__reactRouterManifest);
if (!hasFrameworkGlobals) {
  root = <RouterProvider router={createBrowserRouter(routes)} />;
}

Prevention

When it happens

Trigger: Rendering the framework RouterProvider without a router prop in a manually-built client (Declarative/Data mode app reusing the framework entry); an entry.client.tsx copied into a non-framework setup; SSR pipeline disabled/removed so the hydration globals were never injected.

Common situations: Mixing framework-mode entry files into a custom Vite/webpack client; migrating an app off Framework Mode but keeping the generated entry.client.tsx; test harnesses rendering <RouterProvider /> without the SSR context.

Related errors


AI-assisted analysis of remix-run/react-router@7aea711dd1 (2026-08-18). Data as JSON: /api/errors/1d1006a9a138ed2d. Report an issue: GitHub.