withastro/astro · error · AstroError
ResponseSentError
ResponseSentError
Error message
The response has already been sent to the browser and cannot be altered.
What it means
In streaming (ReadableStream) rendering, once the first chunk is enqueued the status/headers are committed to the client and can no longer change. If a Response object (typically a redirect produced by Astro.redirect) is rendered eagerly and then reaches the stream's write path after output has flushed, Astro throws ResponseSentError instead of silently dropping the redirect.
Solutions
- Move redirects into page frontmatter before any markup is produced, or into middleware (onRequest / next()) where nothing has flushed
- Do auth/session checks in middleware so redirects happen before render starts
- Ensure components/layouts never return a Response; only pages may return one
- If data may arrive late, redirect from middleware or the page's frontmatter await, not from a child component
Example fix
// before: redirect inside a layout
---
if (!user) return Astro.redirect('/login');
---
// after: redirect in middleware
export const onRequest = async (context, next) => {
const user = await getUser(context.request);
if (!user && context.url.pathname.startsWith('/dashboard')) {
return context.redirect('/login');
}
return next();
}; Defensive patterns
Strategy: validation
Validate before calling
// middleware: decide before anything renders
export const onRequest = async (context, next) => {
const user = await getUser(context.request);
if (!user && context.url.pathname.startsWith('/app')) {
return context.redirect('/login');
}
return next();
}; Prevention
- Return Responses only from page frontmatter or middleware — never from layouts, components, or slots
- Put auth and permission checks in middleware so redirects happen before streaming starts
- Await any data that might trigger a redirect before returning markup from frontmatter
When it happens
Trigger: Returning Astro.redirect(...) (or any Response) from a layout, a slot's content, or a nested component rendered after the page's first chunk already flushed; awaiting slow data in frontmatter and returning a redirect only after streaming has begun.
Common situations: Auth checks placed in a layout instead of middleware or page frontmatter; redirects raised from deep inside slot content or try/catch handlers; mixing streamed rendering with control-flow Responses.
Related errors
- OnlyResponseCanBeReturned
- EndpointDidNotReturnAResponse
- GetImageNotUsedOnServer
- NoAdapterInstalledServerIslands
- NoImageMetadata
AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18).
Data as JSON: /api/errors/620cd2daea185186.
Report an issue: GitHub.
Appendix: source
Thrown at packages/astro/src/runtime/server/render/astro/render.ts:96
return new ReadableStream({
start(controller) {
const destination: RenderDestination = {
write(chunk) {
// Automatic doctype insertion for pages
if (isPage && !renderedFirstPageChunk) {
renderedFirstPageChunk = true;
if (!result.partial && !DOCTYPE_EXP.test(String(chunk))) {
const doctype = result.compressHTML ? '<!DOCTYPE html>' : '<!DOCTYPE html>\n';
controller.enqueue(encoder.encode(doctype));
}
}
// `chunk` might be a Response that contains a redirect,
// that was rendered eagerly and therefore bypassed the early check
// whether headers can still be modified. In that case, throw an error
if (chunk instanceof Response) {
throw new AstroError({
...AstroErrorData.ResponseSentError,
});
}
const bytes = chunkToByteArray(result, chunk);
controller.enqueue(bytes);
},
};
(async () => {
try {
await renderStreaming(templateResult, result, destination);
controller.close();
} catch (e) {
// We don't have a lot of information downstream, and upstream we can't catch the error properly
// So let's add the location here
if (AstroError.is(e) && !e.loc) {
e.setLocation({View on GitHub (pinned to 52e6c34790)