remix-run/react-router · error · Error
Cannot call `runClientMiddleware()` from within an `runClien
Error message
Cannot call `runClientMiddleware()` from within an `runClientMiddleware` handler
What it means
Inside the non-static `runClientMiddleware(cb)` implementation, the `cb` is invoked with a `runClientMiddleware` field that always throws. This forbids recursive nesting: a middleware handler cannot itself call `runClientMiddleware` again, which would create an unbounded pipeline.
Source
Thrown at packages/react-router/lib/router/router.ts:6588
};
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",
);
},
});
});
};
let results = await dataStrategyImpl({
...dataStrategyArgs,
fetcherKey,
runClientMiddleware,
});
// Wait for all routes to load here but swallow the error since we want
// it to bubble up from the `await loadRoutePromise` in `callLoaderOrAction` -
// called from `match.resolve()`. We also ensure that all promises are
// awaited so that we don't inadvertently leave any hanging promises.View on GitHub (pinned to 1fd704a7da)
Solutions
- Flatten your middleware pipeline into a single `runClientMiddleware(cb)` call per match.
- If you need ordered wrappers, compose them inside `cb` directly rather than nesting `runClientMiddleware`.
- Move shared logic into a plain function called from `cb`.
Example fix
// before
dataStrategy: (args) =>
args.runClientMiddleware((a2) =>
a2.runClientMiddleware(() => defaultStrategy(args)), // throws
),
// after
dataStrategy: (args) =>
args.runClientMiddleware(() => defaultStrategy(args)), Defensive patterns
Strategy: validation
Validate before calling
let depth = 0;
const safeRunClientMiddleware = (cb) => {
if (++depth > 1) throw new Error('nested runClientMiddleware');
try { return runClientMiddleware(cb); } finally { depth--; }
}; Prevention
- Call `runClientMiddleware` at most once per match.
- Compose ordered middleware inside the callback rather than nesting.
When it happens
Trigger: Calling `runClientMiddleware(...)` from within the callback passed to a previous `runClientMiddleware(...)` call inside a custom client `dataStrategy`.
Common situations: A dataStrategy that wraps each match with middleware and then attempts to wrap it again for a sub-pipeline; recursive helpers that 'add one more middleware layer' by calling runClientMiddleware inside the handler.
Related errors
- You cannot call `runClientMiddleware()` from a static handle
- You may only call `next()` once per middleware
- No value found for context
AI-assisted analysis of remix-run/react-router@1fd704a7da (2026-08-12).
Data as JSON: /api/errors/9175c9b3c641876d.
Report an issue: GitHub.