The Astro Request & Response
When a route is rendered on demand, Astro hands your component the same web-standard primitives the platform itself speaks: a Request coming in and a Response going out. Instead of inventing a bespoke API, Astro exposes the real Request object through Astro.request and lets you influence the outgoing Response through helpers like Astro.response, Astro.redirect(), and direct Response construction. This keeps your code portable across adapters (Node, Cloudflare, Vercel, Deno) and familiar to anyone who has touched the Fetch API. Reading headers, inspecting the URL, parsing form bodies, and customizing status codes all become first-class concerns.
These APIs are only meaningful when a route is rendered per request. In statically generated pages
Astro.requestexists but has no real headers or body, so reach foroutput: 'server', aprerender = falseexport, or on-demand rendering before you depend on them.
Reading the incoming request
Astro.request is a standard Request instance. You get the method, the full URL, and a Headers object for free. For convenience, Astro also surfaces a parsed Astro.url (a URL) and the client IP via Astro.clientAddress.
---
// src/pages/whoami.astro
const { request } = Astro;
const userAgent = request.headers.get("user-agent") ?? "unknown";
const lang = request.headers.get("accept-language")?.split(",")[0] ?? "en";
const ip = Astro.clientAddress;
console.log(`${request.method} ${Astro.url.pathname} from ${ip}`);
---
<section>
<h1>Hello, visitor</h1>
<p>You are browsing with <strong>{userAgent}</strong>.</p>
<p>Preferred language: {lang}</p>
</section>
Output:
GET /whoami from 203.0.113.42
Working with the URL
Astro.url is a URL, so query strings parse cleanly through searchParams without manual string splitting.
---
// src/pages/search.astro
const query = Astro.url.searchParams.get("q") ?? "";
const page = Number(Astro.url.searchParams.get("page") ?? "1");
---
<form method="get">
<input type="search" name="q" value={query} />
</form>
{query && <p>Showing page {page} of results for "{query}"</p>}
Reading the request body
Because Astro.request is a real Request, you can call await request.formData(), await request.json(), or await request.text() directly. This is most common when a route handles a POST from an HTML form or a fetch() call.
---
// src/pages/subscribe.astro
let message = "";
if (Astro.request.method === "POST") {
const data = await Astro.request.formData();
const email = data.get("email");
if (typeof email === "string" && email.includes("@")) {
// persist the subscriber, send a confirmation, etc.
message = `Thanks, ${email} is now subscribed.`;
} else {
Astro.response.status = 400;
message = "Please provide a valid email.";
}
}
---
<form method="POST">
<input type="email" name="email" required />
<button type="submit">Subscribe</button>
</form>
{message && <p>{message}</p>}
Calling
formData()orjson()on a static page throws, because there is no request body to read. Guard body parsing behind a method check and make sure the route is rendered on demand.
Shaping the response
There are two ways to control the outgoing response. For small tweaks to the current page render, mutate Astro.response. For full control, return a brand-new Response from the component frontmatter.
---
// Tweak the in-flight response
Astro.response.status = 201;
Astro.response.statusText = "Created";
Astro.response.headers.set("Cache-Control", "public, max-age=3600");
Astro.response.headers.set("X-Powered-By", "Astro");
---
To short-circuit rendering entirely, return a Response. This is how you emit JSON, plain text, or any custom payload from a .astro page.
---
// src/pages/health.astro
return new Response(JSON.stringify({ ok: true, time: Date.now() }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
---
Redirects
Use Astro.redirect() to send a 3xx response. Pass an optional status (defaults to 302).
---
// src/pages/dashboard.astro
const session = Astro.request.headers.get("cookie")?.includes("session=");
if (!session) {
return Astro.redirect("/login", 307);
}
---
<h1>Welcome back</h1>
Request and response helpers at a glance
| API | Type | Purpose |
|---|---|---|
Astro.request | Request | Method, headers, and body of the incoming request |
Astro.url | URL | Parsed request URL with searchParams |
Astro.clientAddress | string | Client IP (adapter-dependent) |
Astro.response.status | number | Status code for the current render |
Astro.response.headers | Headers | Mutable outgoing headers |
Astro.redirect(path, status?) | Response | Returns a redirect response |
return new Response(...) | Response | Fully custom payload, status, and headers |
Best practices
- Treat
Astro.requestas the web-standardRequestit is — lean onHeaders,formData(), andURLinstead of hand-rolled parsing. - Guard body reads behind a
Astro.request.method === "POST"check so static orGETrenders never throw. - Mutate
Astro.responsefor incremental header and status changes; return a freshResponseonly when you need to bypass HTML rendering. - Always set an explicit
Content-Typewhen returning JSON or text from aResponse. - Set sensible
Cache-Controlheaders on on-demand routes to recover the performance you would get from static output. - Remember
Astro.clientAddressand header availability depend on your adapter — test on the platform you deploy to.