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

On the fetch-state-backed Astro global, Astro.response.headers is defined with a getter only; the setter always throws AstroErrorData.AstroResponseHeadersReassigned. Astro forbids replacing the Headers instance because the rest of the render pipeline (cookies, actions, routing) already holds a reference to that exact object. Individual headers may still be added, changed, or removed in place.

Solutions

  1. Mutate the existing instance in place: Astro.response.headers.set('name', 'value')
  2. If you built a Headers or plain object elsewhere, copy its entries onto Astro.response.headers with set/append instead of assigning
  3. Use Astro.response.status / statusText for the other response fields — only the headers instance is protected

Example fix

// before
Astro.response.headers = new Headers({ 'x-cache': 'miss' });

// after
Astro.response.headers.set('x-cache', 'miss');
Defensive patterns

Strategy: validation

Validate before calling

function applyResponseHeaders(source: Headers | Record<string, string>): void {
  const target = Astro.response.headers;
  if (source instanceof Headers) {
    for (const [name, value] of source) target.set(name, value);
  } else {
    for (const [name, value] of Object.entries(source)) target.set(name, value);
  }
}
// use: applyResponseHeaders(new Headers({ 'x-cache': 'miss' })) instead of assigning

Prevention

When it happens

Trigger: Any whole-object assignment in .astro frontmatter, middleware, or actions: Astro.response.headers = new Headers({...}), or assigning a Headers object built elsewhere (a helper function that returns new Headers() and is then assigned).

Common situations: Porting code from Express/Next-style frameworks where replacing the response or its headers is idiomatic; refactors that build a Headers object in a utility and assign it on the Astro global; copy-pasted snippets from tutorials targeting other stacks.

Related errors


AI-assisted analysis of withastro/astro@e294953aa8 (2026-08-18). Data as JSON: /api/errors/3156b2d1b99a0495. Report an issue: GitHub.

Appendix: source

Thrown at packages/astro/src/core/fetch/fetch-state.ts:441

			}
		}

		const componentMetadata =
			(await env.componentMetadata(manifest, 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 e294953aa8)