remix-run/react-router · error

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

Error message

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

What it means

Each route middleware receives a next() function that resumes the pipeline at the next middleware or the handler; the guard in callRouteMiddleware (lib/router/router.ts:6334) allows exactly one invocation per middleware execution. Calling it a second time (wrong branch structure, retries, loops) throws this error.

Solutions

  1. Call next() exactly once per code path: capture const result = await next() and branch on the result instead of re-calling
  2. Do not call next() in both try and catch of the same try/catch
  3. For retry semantics, put the retryable work inside the handler/route, not around next()
  4. Return a value (or throw a Response) instead of calling next() when you want to short-circuit

Example fix

// before (second next() throws)
try {
  return await next()
} catch {
  return await next()
}
// after
const result = await next()
if (result instanceof Response && result.status === 503) {
  // handle or throw, but never call next() again
}
return result
Defensive patterns

Strategy: validation

Validate before calling

// Wrap next() so double calls fail fast with your own stack trace
function once<T extends (...a: any[]) => any>(fn: T): T {
  let called = false;
  return ((...a: Parameters<T>) => {
    if (called) throw new Error('next() already called');
    called = true;
    return fn(...a);
  }) as T;
}
export const mw = async ({ next }, _once = once(next)) => { /* use _once everywhere */ }

Prevention

When it happens

Trigger: Both branches of an if/else call next(); calling next() again inside a catch block after the first call failed; a loop that calls next() per iteration; retry logic that awaits next() twice to re-run downstream stages.

Common situations: Auth middleware that calls next() on success and also falls through to a second next(); cache middleware attempting retry on transient errors; refactoring middleware from a wrapper function into next()-style and leaving a stray call.

Related errors


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

Appendix: source

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

  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 7aea711dd1)