{"record":{"id":"14637cdcd771ae97","repo":"remix-run/react-router","slug":"you-may-only-call-next-once-per-middleware","errorCode":null,"errorMessage":"You may only call `next()` once per middleware","messagePattern":"You may only call `next\\(\\)` once per middleware","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/react-router/lib/router/router.ts","lineNumber":6313,"sourceCode":"  if (request.signal.aborted) {\n    throw (\n      request.signal.reason ??\n      new Error(`Request aborted: ${request.method} ${request.url}`)\n    );\n  }\n\n  let tuple = middlewares[idx];\n  if (!tuple) {\n    // We reached the end of our middlewares, call the handler\n    let result = await handler();\n    return result;\n  }\n\n  let [routeId, middleware] = tuple;\n  let nextResult: { value: Result } | undefined;\n  let next: MiddlewareNextFunction<Result> = async () => {\n    if (nextResult) {\n      throw new Error(\"You may only call `next()` once per middleware\");\n    }\n\n    try {\n      let result = await callRouteMiddleware(\n        args,\n        middlewares,\n        handler,\n        processResult,\n        isResult,\n        errorHandler,\n        idx + 1,\n      );\n\n      nextResult = { value: result };\n      return nextResult.value;\n    } catch (error) {\n      nextResult = { value: await errorHandler(error, routeId, nextResult) };\n      return nextResult.value;","sourceCodeStart":6295,"sourceCodeEnd":6331,"githubUrl":"https://github.com/remix-run/react-router/blob/7aea711dd1ae2bc5a076d13ff17291829690fa74/packages/react-router/lib/router/router.ts#L6295-L6331","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Call next() exactly once per code path: capture const result = await next() and branch on the result instead of re-calling","Do not call next() in both try and catch of the same try/catch","For retry semantics, put the retryable work inside the handler/route, not around next()","Return a value (or throw a Response) instead of calling next() when you want to short-circuit"],"exampleFix":"// before (second next() throws)\ntry {\n  return await next()\n} catch {\n  return await next()\n}\n// after\nconst result = await next()\nif (result instanceof Response && result.status === 503) {\n  // handle or throw, but never call next() again\n}\nreturn result","handlingStrategy":"validation","validationCode":"// Wrap next() so double calls fail fast with your own stack trace\nfunction once<T extends (...a: any[]) => any>(fn: T): T {\n  let called = false;\n  return ((...a: Parameters<T>) => {\n    if (called) throw new Error('next() already called');\n    called = true;\n    return fn(...a);\n  }) as T;\n}\nexport const mw = async ({ next }, _once = once(next)) => { /* use _once everywhere */ }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Have exactly one `await next()` call site per middleware and branch on its result","Never call next() in both the try and catch of the same try/catch","Put retry logic inside handlers/routes, not around next()","Short-circuit by returning or throwing a Response instead of extra next() calls"],"tags":["middleware","next","pipeline","api-misuse"],"backgroundTag":"middleware-next-called-twice","analyzedSha":"7aea711dd1ae2bc5a076d13ff17291829690fa74","analyzedAt":"2026-08-18T18:04:14.938Z","contentChangedAt":"2026-08-18T18:04:14.938Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}