Skip to content
Astro as rendering 4 min read

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.request exists but has no real headers or body, so reach for output: 'server', a prerender = false export, 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() or json() 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

APITypePurpose
Astro.requestRequestMethod, headers, and body of the incoming request
Astro.urlURLParsed request URL with searchParams
Astro.clientAddressstringClient IP (adapter-dependent)
Astro.response.statusnumberStatus code for the current render
Astro.response.headersHeadersMutable outgoing headers
Astro.redirect(path, status?)ResponseReturns a redirect response
return new Response(...)ResponseFully custom payload, status, and headers

Best practices

  • Treat Astro.request as the web-standard Request it is — lean on Headers, formData(), and URL instead of hand-rolled parsing.
  • Guard body reads behind a Astro.request.method === "POST" check so static or GET renders never throw.
  • Mutate Astro.response for incremental header and status changes; return a fresh Response only when you need to bypass HTML rendering.
  • Always set an explicit Content-Type when returning JSON or text from a Response.
  • Set sensible Cache-Control headers on on-demand routes to recover the performance you would get from static output.
  • Remember Astro.clientAddress and header availability depend on your adapter — test on the platform you deploy to.
Last updated June 14, 2026
Was this helpful?