remix-run/remix · error · Error
next() called multiple times
Error message
next() called multiple times
What it means
Express-style middleware in fetch-router receives a next() function; calling next() more than once per middleware breaks the dispatch chain, so the runner guards it with an index check and throws this error. Each middleware may advance to the next layer exactly one time.
Source
Thrown at packages/fetch-router/src/lib/middleware.ts:112
return middleware
}
/**
* A function that invokes the next middleware or handler in the chain.
*
* @returns The response from the downstream handler
*/
export type NextFunction = () => Promise<Response>
export function runMiddleware(
middleware: AnyMiddleware[],
context: RequestContext<any, any>,
handler: RequestHandler<any>,
): Promise<Response> {
let index = -1
let dispatch = async (i: number): Promise<Response> => {
if (i <= index) throw new Error('next() called multiple times')
index = i
if (context.request.signal.aborted) {
throw context.request.signal.reason
}
let fn = middleware[i]
if (!fn) {
return await raceRequestAbort(Promise.resolve(handler(context)), context.request)
}
let nextPromise: Promise<Response> | undefined
let next: NextFunction = () => {
nextPromise = dispatch(i + 1)
return nextPromise
}
let response = await raceRequestAbort(Promise.resolve(fn(context, next)), context.request)View on GitHub (pinned to 9696913134)
Solutions
- Restructure the middleware so next() is called exactly once on every path
- Await next() once and reuse its Response for any additional logic
- For conditional work after downstream handling, capture let res = await next() then post-process
Example fix
// before
async function mw(ctx, next) {
if (a) await next()
await next() // always runs again
}
// after
async function mw(ctx, next) {
let res = await next()
if (a) log(res)
return res
} Defensive patterns
Strategy: validation
Validate before calling
let called = false
const nextOnce = () => { if (called) throw new Error('double next'); called = true; return next() } Try / catch
try { return await next() } catch (e) { if (e instanceof Error && e.message.includes('next() called multiple times')) { /* fix middleware logic */ } throw e } Prevention
- Call next() exactly once per middleware; store its Response for reuse
- Return the result of next() rather than calling it again in branches
When it happens
Trigger: Calling await next() twice in one middleware, or calling next() in a loop/recursive helper; also calling a stale next() captured earlier after the chain moved on.
Common situations: Middleware that branches (if/else both call next), retries, or fire-and-forget code paths that invoke next() inside setTimeout/event handlers without awaiting exactly once.
Related errors
- Middleware must return a Response or call next()
- Context property name must be a string.
- Cannot install an empty context property name.
- Cannot install context property {formattedProperty} because
- Cannot install context property {formattedProperty} because
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/4420d4aeb13852ca.
Report an issue: GitHub.