remix-run/react-router · error · Error

You may only call `next()` once per middleware

Error message

You may only call `next()` once per middleware

What it means

Middleware's `next()` function records whether it has already been invoked. Calling `next()` a second time inside the same middleware throws, because double-advancing the chain would duplicate downstream work and break the result/error handling invariants.

Source

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

  if (request.signal.aborted) {
    throw (
      request.signal.reason ??
      new Error(`Request aborted: ${request.method} ${request.url}`)
    );
  }

  let tuple = middlewares[idx];
  if (!tuple) {
    // We reached the end of our middlewares, call the handler
    let result = await handler();
    return result;
  }

  let [routeId, middleware] = tuple;
  let nextResult: { value: Result } | undefined;
  let next: MiddlewareNextFunction<Result> = async () => {
    if (nextResult) {
      throw new Error("You may only call `next()` once per middleware");
    }

    try {
      let result = await callRouteMiddleware(
        args,
        middlewares,
        handler,
        processResult,
        isResult,
        errorHandler,
        idx + 1,
      );

      nextResult = { value: result };
      return nextResult.value;
    } catch (error) {
      nextResult = { value: await errorHandler(error, routeId, nextResult) };
      return nextResult.value;

View on GitHub (pinned to 1fd704a7da)

Solutions

  1. Call `next()` exactly once per middleware invocation; store its result if you need to read/modify it.
  2. Use `try/finally` only for cleanup (no second `next()`); reuse the captured `const result = await next()`.
  3. Run your middleware in a unit test that asserts the chain executes once.

Example fix

// before
export const middleware = async ({ request }, next) => {
  await next();
  await next(); // throws
};

// after
export const middleware = async ({ request }, next) => {
  const res = await next();
  res.headers.set('x-mw', '1');
  return res;
};
Defensive patterns

Strategy: validation

Validate before calling

function once<T extends (...a: any[]) => any>(fn: T): T {
  let called = false;
  return ((...args: any[]) => {
    if (called) throw new Error('next() already called');
    called = true;
    return fn(...args);
  }) as T;
}
const safeNext = once(next);

Prevention

When it happens

Trigger: A route `middleware` export that calls `await next()` twice in the same invocation: once in a try block and once in a finally, or in both branches of an `if/else`, or accidentally after awaiting it.

Common situations: Copy-pasting logging middleware that calls `next()` at the top and again at the bottom; a `try/finally` that calls `next()` in both; refactoring that loses track of which branch already advanced the chain.

Related errors


AI-assisted analysis of remix-run/react-router@1fd704a7da (2026-08-12). Data as JSON: /api/errors/14637cdcd771ae97. Report an issue: GitHub.