withastro/astro · error · AstroError

ResponseSentError

ResponseSentError

Error message

The response has already been sent to the browser and cannot be altered.

What it means

`Astro.cookies.set()` serializes the cookie into the outgoing map, then checks the internal `Symbol.for('astro.responseSent')` flag on the request. Once the response pipeline has marked that flag (in `prepare-response` or the dev server), headers can no longer be altered and `set()` throws `ResponseSentError`. A mere `console.warn` fires when cookies were consumed (e.g. set inside an imported component); the throw is specifically for after the response was actually sent.

Solutions

  1. Move `Astro.cookies.set()` before any `return`/`Astro.redirect()` in the same frontmatter block.
  2. Set cookies in middleware (`onRequest`) or in the page frontmatter — never in imported components or slots.
  3. If the value only becomes known late, restructure the route: do the async work first, then set the cookie, then return the response.
  4. As a last resort in library code, catch the error and degrade to logging instead of crashing the render.

Example fix

// before — set() runs after the redirect response was sent
if (!user) return Astro.redirect('/login');
Astro.cookies.set('lastPath', Astro.url.pathname); // throws ResponseSentError

// after — set cookies before returning the response
Astro.cookies.set('lastPath', Astro.url.pathname);
if (!user) return Astro.redirect('/login');
Defensive patterns

Strategy: type-guard

Validate before calling

// Mirror the internal flag Astro sets on the request once the response leaves
const responseSent = Reflect.get(Astro.request as object, Symbol.for('astro.responseSent')) === true;
if (!responseSent) {
  Astro.cookies.set('session', token);
}

Type guard

// True while Set-Cookie headers can still be emitted
function canStillSetCookies(request: Request): boolean {
  return Reflect.get(request, Symbol.for('astro.responseSent')) !== true;
}

Try / catch

try {
  Astro.cookies.set('flag', '1');
} catch (err) {
  if (err instanceof Error && err.name === 'ResponseSentError') {
    logger.warn('Cookie dropped: response already sent');
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling `Astro.cookies.set()` after returning/redirecting (e.g. after `return Astro.redirect('/login')`), inside a component rendered after headers flushed, or during streaming when the body has already started and some awaited code then sets a cookie.

Common situations: Login/auth flows that set a session cookie after the redirect statement; cookie logic placed in a layout or imported component instead of the page frontmatter; slow async work in a streamed page that tries to set cookies at the end.

Related errors


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

Appendix: source

Thrown at packages/astro/src/core/cookies/cookies.ts:210

		}

		const { encode, ...attributes } = options ?? {};

		this.#ensureOutgoingMap().set(key, [
			serializedValue,
			stringifySetCookie(
				{
					...attributes,
					name: key,
					value: serializedValue,
				},
				{ encode },
			),
			true,
		]);

		if ((this.#request as any)[responseSentSymbol]) {
			throw new AstroError({
				...AstroErrorData.ResponseSentError,
			});
		}
	}

	/**
	 * Merges a new AstroCookies instance into the current instance. Any new cookies
	 * will be added to the current instance, overwriting any existing cookies with the same name.
	 */
	merge(cookies: AstroCookies) {
		const outgoing = cookies.#outgoing;
		if (outgoing) {
			for (const [key, value] of outgoing) {
				this.#ensureOutgoingMap().set(key, value);
			}
		}
	}

View on GitHub (pinned to e294953aa8)