{"record":{"id":"b802a670cb7cc127","repo":"withastro/astro","slug":"responsesenterror-b802a6","errorCode":"ResponseSentError","errorMessage":"The response has already been sent to the browser and cannot be altered.","messagePattern":"The response has already been sent to the browser and cannot be altered\\.","errorType":"exception","errorClass":"AstroError","httpStatus":null,"severity":"error","filePath":"packages/astro/src/core/fetch/fetch-state.ts","lineNumber":519,"sourceCode":"\t\t\t},\n\t\t});\n\n\t\treturn Astro as AstroGlobal;\n\t}\n\n\t/**\n\t * Creates the Astro page-level partial (prototype for Astro global).\n\t */\n\tcreateAstroPagePartial(\n\t\tresult: SSRResult,\n\t\tapiContext: ActionAPIContext,\n\t): Omit<AstroGlobal, 'props' | 'self' | 'slots'> {\n\t\tconst state = this;\n\t\tconst { cookies, locals, params, pipeline, url } = this;\n\t\tconst { response } = result;\n\t\tconst redirect = (path: string, status = 302) => {\n\t\t\tif ((state.request as any)[responseSentSymbol]) {\n\t\t\t\tthrow new AstroError({\n\t\t\t\t\t...AstroErrorData.ResponseSentError,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn new Response(null, { status, headers: { Location: path } });\n\t\t};\n\n\t\tconst rewrite = async (reroutePayload: RewritePayload) => {\n\t\t\treturn await state.rewrite(reroutePayload);\n\t\t};\n\n\t\tconst callAction = createCallAction(apiContext);\n\n\t\tconst partial: Record<string, any> = {\n\t\t\tgenerator: ASTRO_GENERATOR,\n\t\t\troutePattern: this.routeData!.route,\n\t\t\tisPrerendered: this.routeData!.prerender,\n\t\t\tcookies,\n\t\t\tget clientAddress() {","sourceCodeStart":501,"sourceCodeEnd":537,"githubUrl":"https://github.com/withastro/astro/blob/d081033d5fe8e8a68c4bbbad4af9d2deb9c74bca/packages/astro/src/core/fetch/fetch-state.ts#L501-L537","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Move the Astro.redirect() call before any code that streams or returns a Response, so it runs before the response is committed.","If you called an action, do not also call redirect in the same flow; return the action's Response or the redirect, never both.","Check whether the response was already sent (avoid double-issuing) and branch: return early once a redirect or Response has been produced.","Restructure streaming pages so redirects happen during the initial routing/middleware phase rather than mid-render."],"exampleFix":"// before\nconst result = await callMyAction();\nreturn Astro.redirect('/done'); // response already sent by action\n\n// after\nif (!actionResponded) {\n  return Astro.redirect('/done');\n}\nreturn result;","handlingStrategy":"validation","validationCode":"// Before calling redirect, ensure no response was sent.\n// Astro does not expose responseSentSymbol publicly, so the rule is structural:\n// do not call redirect after any operation that returns/streams a Response.\nconst actionResponse = await runAction();\nif (actionResponse && actionResponse.status !== 200) {\n  // already have a committed response, do NOT redirect\n  return actionResponse;\n}\nreturn Astro.redirect('/next');","typeGuard":"// No public symbol; treat any returned Response as 'response sent'.\nfunction hasResponseAlready(pending: unknown): pending is Response {\n  return pending instanceof Response;\n}","tryCatchPattern":"try {\n  return Astro.redirect('/done');\n} catch (e) {\n  if (e instanceof Error && /already been sent/i.test(e.message)) {\n    // response committed; return the existing response instead\n    return existingResponse;\n  }\n  throw e;\n}","preventionTips":["Call Astro.redirect() exactly once per request, before any streaming or action response.","Treat any returned Response from an action as a committed response.","Centralize redirect logic in one place (middleware or page), not both."],"tags":["redirect","response","lifecycle","ssr","actions"],"backgroundTag":null,"analyzedSha":"d081033d5fe8e8a68c4bbbad4af9d2deb9c74bca","analyzedAt":"2026-08-12T13:37:29.035Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}