{"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":"Error","httpStatus":null,"severity":"error","filePath":"packages/react-router/lib/router/router.ts","lineNumber":6334,"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":6316,"sourceCodeEnd":6352,"githubUrl":"https://github.com/remix-run/react-router/blob/1fd704a7dabcbe3ae09d7387b460e6acaba30ec1/packages/react-router/lib/router/router.ts#L6316-L6352","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Call `next()` exactly once per middleware invocation; store its result if you need to read/modify it.","Use `try/finally` only for cleanup (no second `next()`); reuse the captured `const result = await next()`.","Run your middleware in a unit test that asserts the chain executes once."],"exampleFix":"// before\nexport const middleware = async ({ request }, next) => {\n  await next();\n  await next(); // throws\n};\n\n// after\nexport const middleware = async ({ request }, next) => {\n  const res = await next();\n  res.headers.set('x-mw', '1');\n  return res;\n};","handlingStrategy":"validation","validationCode":"function once<T extends (...a: any[]) => any>(fn: T): T {\n  let called = false;\n  return ((...args: any[]) => {\n    if (called) throw new Error('next() already called');\n    called = true;\n    return fn(...args);\n  }) as T;\n}\nconst safeNext = once(next);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Call `next()` exactly once per middleware.","Reuse `const result = await next()` instead of awaiting twice.","Unit-test middleware to assert single advancement."],"tags":["middleware","router","next","control-flow"],"backgroundTag":null,"analyzedSha":"1fd704a7dabcbe3ae09d7387b460e6acaba30ec1","analyzedAt":"2026-08-12T13:54:57.804Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}