withastro/astro · error · Error
FetchState not found on APIContext. `next(payload)` rewrites
Error message
FetchState not found on APIContext. `next(payload)` rewrites require a context created through Astro's request pipeline.
What it means
sequence()'s rewrite form — next('/path') — retrieves the FetchState that Astro's request pipeline stamps on the context under a private symbol. Contexts created any other way (notably createContext() contexts used by adapter edge-middleware bridges) never carry that symbol, so the rewrite cannot be routed and this Error is thrown: the context must come through Astro's request pipeline for payload rewrites.
Source
Thrown at packages/astro/src/core/middleware/sequence.ts:47
/**
* This variable is used to carry the rerouting payload across middleware functions.
*/
let carriedPayload: RewritePayload | undefined = undefined;
return applyHandle(0, context);
function applyHandle(i: number, handleContext: APIContext) {
const handle = filtered[i];
// @ts-expect-error
// SAFETY: Usually `next` always returns something in user land, but in `sequence` we are actually
// doing a loop over all the `next` functions, and eventually we call the last `next` that returns the `Response`.
const result = handle(handleContext, async (payload?: RewritePayload) => {
if (i < length - 1) {
if (payload) {
const oldPathname = handleContext.url.pathname;
const state = Reflect.get(handleContext, fetchStateSymbol) as FetchState | undefined;
if (!state) {
// Outside Astro's request pipeline the state is never stamped.
throw new Error(
"FetchState not found on APIContext. `next(payload)` rewrites require a context created through Astro's request pipeline.",
);
}
const manifest = state.manifest;
const { routeData, pathname } = await getEnvironment(manifest).tryRewrite(
manifest,
payload,
handleContext.request,
);
let newRequest: Request;
if (payload instanceof Request) {
newRequest = payload;
} else {
const request =
handleContext.request.method === 'GET' || handleContext.request.method === 'HEAD'
? handleContext.request
: handleContext.request.clone();
const newUrl =View on GitHub (pinned to 157c500c38)
Solutions
- In edge middleware, use the platform's native rewrite mechanism instead of next('/path')
- Return a redirect Response (context.redirect(target)) instead of rewriting — redirects do not need the pipeline state
- Perform Astro rewrites from server-rendered pipeline middleware, not from adapter-bridged contexts
- In tests, exercise the full pipeline (app.render) rather than calling sequence() with a bare context
Example fix
// before (edge middleware)
return next('/dashboard');
// after
return context.redirect('/dashboard', 302); Defensive patterns
Strategy: try-catch
Try / catch
try {
return next('/dashboard');
} catch (err) {
if (err instanceof Error && err.message.includes('FetchState not found on APIContext')) {
// edge-bridged context — fall back to a redirect, which needs no pipeline state
return context.redirect('/dashboard', 302);
}
throw err;
} Prevention
- In adapter edge middleware, use the platform's native rewrite instead of next('/path')
- Reserve Astro payload rewrites for middleware running inside the server pipeline
- Test middleware under the same context type the adapter will actually use
When it happens
Trigger: Middleware running under an adapter's own context (Vercel/Netlify edge middleware chain) calls next('/some-route'); invoking sequence() manually in tests or a custom host with a fabricated context and using the rewrite payload form.
Common situations: Moving server-side middleware that uses next(payload) rewrites to the edge; adapters that run the middleware chain outside app.render; unit tests driving sequence() with a hand-built context.
Related errors
- RewriteWithBodyUsed
- NoManifestAvailableError
- MiddlewareNotAResponse
- MiddlewareNoDataOrNextCalled
- MissingMiddlewareForInternationalization
AI-assisted analysis of withastro/astro@157c500c38 (2026-08-18).
Data as JSON: /api/errors/99309e1661fa50fa.
Report an issue: GitHub.