remix-run/react-router · error

You cannot call `runClientMiddleware()` from a static…

Error message

You cannot call `runClientMiddleware()` from a static handler `dataStrategy`. Middleware is run outside of `dataStrategy` during SSR in order to bubble up the Response.  You can enable middleware via the `respond` API in `query`/`queryRoute`

What it means

In SSR, middleware runs outside dataStrategy so that middleware Responses (redirects, errors) can bubble up as the HTTP response. A static handler's dataStrategy therefore receives a runClientMiddleware that is a throwing stub (lib/router/router.ts:6573); calling it means a browser-oriented dataStrategy was reused on the server.

Solutions

  1. Split the strategy per environment: only call runClientMiddleware when running in the browser (typeof window !== 'undefined')
  2. On the server, let query()/queryRoute() drive middleware themselves (respond API) instead of calling it from dataStrategy
  3. Pass a separate dataStrategy option to createStaticHandler vs createBrowserRouter

Example fix

// before
const dataStrategy = async (args) => args.runClientMiddleware(async () => args.fetch())
createBrowserRouter({ dataStrategy })
createStaticHandler({ dataStrategy }) // throws in SSR
// after
const dataStrategy = async (args) =>
  typeof window === 'undefined'
    ? args.fetch()
    : args.runClientMiddleware(async () => args.fetch())
Defensive patterns

Strategy: type-guard

Validate before calling

const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
const dataStrategy = async (args) =>
  isBrowser ? args.runClientMiddleware(() => args.fetch()) : args.fetch();

Type guard

function isStaticHandlerArgs(args: DataStrategyFunctionArgs): boolean {
  return typeof window === 'undefined';
}

Prevention

When it happens

Trigger: Sharing one custom dataStrategy between createBrowserRouter and createStaticHandler and unconditionally calling args.runClientMiddleware(...); a fetcher/handler strategy copied from client code into an express/node SSR setup; invoking the single-fetch default strategy shape inside query() with middleware enabled.

Common situations: Custom dataStrategy with tracing/caching wrappers deployed to both environments; integration tests running the client strategy through a static handler; enabling middleware after the dataStrategy was already written for client-only behavior.

Related errors


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

Appendix: source

Thrown at packages/react-router/lib/router/router.ts:6552

  }

  // Send all matches here to allow for a middleware-type implementation.
  // handler will be a no-op for unneeded routes and we filter those results
  // back out below.
  let dataStrategyArgs: Omit<
    DataStrategyFunctionArgs<unknown>,
    "fetcherKey" | "runClientMiddleware"
  > = {
    request,
    url: createDataFunctionUrl(request, path),
    pattern: getRoutePattern(matches),
    params: matches[0].params,
    context: scopedContext,
    matches,
  };
  let runClientMiddleware = isStaticHandler
    ? () => {
        throw new Error(
          "You cannot call `runClientMiddleware()` from a static handler " +
            "`dataStrategy`. Middleware is run outside of `dataStrategy` during " +
            "SSR in order to bubble up the Response.  You can enable middleware " +
            "via the `respond` API in `query`/`queryRoute`",
        );
      }
    : (cb: DataStrategyFunction<RouterContextProvider>) => {
        let typedDataStrategyArgs =
          dataStrategyArgs as DataStrategyFunctionArgs<RouterContextProvider>;
        return runClientMiddlewarePipeline(typedDataStrategyArgs, () => {
          return cb({
            ...typedDataStrategyArgs,
            fetcherKey,
            runClientMiddleware: () => {
              throw new Error(
                "Cannot call `runClientMiddleware()` from within an " +
                  "`runClientMiddleware` handler",
              );

View on GitHub (pinned to 7aea711dd1)