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

  1. Mutate in place: call `Astro.response.headers.set('X', 'y')`, `.append(...)`, or `.delete(...)`.
  2. If you have a pre-built `Headers` object, iterate and `set` each entry onto `Astro.response.headers`.
  3. 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

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


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