withastro/astro · error · AstroError

ResponseSentError

ResponseSentError

Error message

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

What it means

Thrown when Astro.redirect() is called after the HTTP response for the current request has already been flushed to the browser. The responseSentSymbol on the request marks a response as committed, so a second redirect would mutate state the client already received. Astro enforces this to prevent corrupt or duplicate responses.

Source

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

			},
		});

		return Astro as AstroGlobal;
	}

	/**
	 * Creates the Astro page-level partial (prototype for Astro global).
	 */
	createAstroPagePartial(
		result: SSRResult,
		apiContext: ActionAPIContext,
	): Omit<AstroGlobal, 'props' | 'self' | 'slots'> {
		const state = this;
		const { cookies, locals, params, pipeline, url } = this;
		const { response } = result;
		const redirect = (path: string, status = 302) => {
			if ((state.request as any)[responseSentSymbol]) {
				throw new AstroError({
					...AstroErrorData.ResponseSentError,
				});
			}
			return new Response(null, { status, headers: { Location: path } });
		};

		const rewrite = async (reroutePayload: RewritePayload) => {
			return await state.rewrite(reroutePayload);
		};

		const callAction = createCallAction(apiContext);

		const partial: Record<string, any> = {
			generator: ASTRO_GENERATOR,
			routePattern: this.routeData!.route,
			isPrerendered: this.routeData!.prerender,
			cookies,
			get clientAddress() {

View on GitHub (pinned to d081033d5f)

Solutions

  1. Move the Astro.redirect() call before any code that streams or returns a Response, so it runs before the response is committed.
  2. If you called an action, do not also call redirect in the same flow; return the action's Response or the redirect, never both.
  3. Check whether the response was already sent (avoid double-issuing) and branch: return early once a redirect or Response has been produced.
  4. Restructure streaming pages so redirects happen during the initial routing/middleware phase rather than mid-render.

Example fix

// before
const result = await callMyAction();
return Astro.redirect('/done'); // response already sent by action

// after
if (!actionResponded) {
  return Astro.redirect('/done');
}
return result;
Defensive patterns

Strategy: validation

Validate before calling

// Before calling redirect, ensure no response was sent.
// Astro does not expose responseSentSymbol publicly, so the rule is structural:
// do not call redirect after any operation that returns/streams a Response.
const actionResponse = await runAction();
if (actionResponse && actionResponse.status !== 200) {
  // already have a committed response, do NOT redirect
  return actionResponse;
}
return Astro.redirect('/next');

Type guard

// No public symbol; treat any returned Response as 'response sent'.
function hasResponseAlready(pending: unknown): pending is Response {
  return pending instanceof Response;
}

Try / catch

try {
  return Astro.redirect('/done');
} catch (e) {
  if (e instanceof Error && /already been sent/i.test(e.message)) {
    // response committed; return the existing response instead
    return existingResponse;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling Astro.redirect() from within an endpoint or page after a streaming response has started, after calling an action that already sent its response, or invoking redirect twice in the same request lifecycle (e.g. once in middleware and again in the page). The check reads (request as any)[responseSentSymbol] inside the redirect function defined in createAstroPagePartial.

Common situations: Mixing Astro Actions (which write their own Response) with a subsequent Astro.redirect in the same handler; using redirect inside a layout that renders after streaming has begun; redirecting conditionally after a fetch/action call where the response body was already written.

Related errors


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