withastro/astro · error · AstroError
AstroResponseHeadersReassigned
AstroResponseHeadersReassigned
Error message
Individual headers can be added to and removed from `Astro.response.headers`, but it must not be replaced with another instance of `Headers` altogether.
What it means
`Astro.response` exposes a getter `headers` returning the live `Headers` object, but the setter is overridden to throw `AstroResponseHeadersReassigned` on any assignment. This enforces mutation-only access (`append`/`delete`/`set`) so the underlying headers instance bound to the response pipeline stays consistent; replacing it would orphan the bound headers.
Source
Thrown at packages/astro/src/core/fetch/fetch-state.ts:383
}
}
const componentMetadata =
(await pipeline.componentMetadata(routeData)) ?? manifest.componentMetadata;
const headers = new Headers({ 'Content-Type': 'text/html' });
const partial = typeof this.partial === 'boolean' ? this.partial : Boolean(mod.partial);
const actionResult = hasActionPayload(this.locals)
? deserializeActionResult(this.locals._actionPayload.actionResult)
: undefined;
const status = this.status;
const response = {
status: actionResult?.error ? actionResult?.error.status : status,
statusText: actionResult?.error ? actionResult?.error.type : 'OK',
get headers() {
return headers;
},
set headers(_) {
throw new AstroError(AstroErrorData.AstroResponseHeadersReassigned);
},
} satisfies AstroGlobal['response'];
const state = this;
const result: SSRResult = {
base: manifest.base,
userAssetsBase: manifest.userAssetsBase,
cancelled: false,
clientDirectives,
inlinedScripts,
componentMetadata,
compressHTML,
cookies: this.cookies,
createAstro: (props, slots) => state.createAstro(result, props, slots, ctx),
links,
// SAFETY: createResult is only called after route resolution, so routeData
// is always set and the params getter always returns a value.
params: this.params!,View on GitHub (pinned to d081033d5f)
Solutions
- Mutate in place: call `Astro.response.headers.set('X', 'y')`, `.append(...)`, or `.delete(...)`.
- If you have a pre-built `Headers` object, iterate and `set` each entry onto `Astro.response.headers`.
- Use `Astro.response.status`/`.statusText` for status changes — only `headers` is non-reassignable.
Example fix
// before
Astro.response.headers = new Headers({ 'x-cache': 'miss' });
// after
Astro.response.headers.set('x-cache', 'miss'); Defensive patterns
Strategy: type-guard
Type guard
// Astro.response.headers is non-reassignable by contract; always mutate in place.
Try / catch
try { Astro.response.headers.set('x', 'y'); } catch (e) { if (e.code === 'AstroResponseHeadersReassigned') { /* impossible via set(); assignment was made */ } else throw e; } Prevention
- Never assign to Astro.response.headers; only call set/append/delete.
- When porting code, translate res.headers = X into per-header set() calls.
- Document the headers-mutation contract for new contributors.
When it happens
Trigger: Writing `Astro.response.headers = new Headers(...)` or `Astro.response.headers = someOtherHeaders` in an `.astro` page, layout, middleware, or API route. The setter at `fetch-state.ts:383` throws immediately.
Common situations: Porting Express-style code that reassigns `res.headers`; misunderstanding the API and assuming `headers` is assignable; trying to swap in a pre-built `Headers` from elsewhere.
Related errors
- Cannot convert undefined to an object.
- ResponseSentError
- UnknownContentCollectionError
- RenderUndefinedEntryError
- CacheNotEnabled
AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12).
Data as JSON: /api/errors/3156b2d1b99a0495.
Report an issue: GitHub.