Type something to search...
What is the difference between static and on-demand rendering in Astro?

What is the difference between static and on-demand rendering in Astro?

The difference between static and on-demand rendering in Astro is when the HTML is created. With static rendering (also called prerendering), Astro runs your page code once during astro build and writes a finished .html file that every visitor receives. With on-demand rendering (also called server-side rendering or SSR), the page code runs on a server for every request, so the HTML can depend on cookies, headers, the URL or fresh data. Static is Astro's default and needs nothing extra; on-demand rendering requires an adapter and is enabled per page with export const prerender = false, or for the whole site with output: 'server'.

Both modes use the same .astro components and the same routing, so choosing between them is about where and when code runs, not how you write it. In this article you'll see the two modes side by side with real build output, what each one can and can't do, how they differ in performance, cost and hosting, how to mix them in one project, and a practical guide to choosing. All examples were built with Astro 7.4.2 and the @astrojs/node 11.1.9 adapter.

The Same Page, Two Ways

Here's a page that uses Astro's default, static rendering:

---
// src/pages/index.astro
const builtAt = new Date().toISOString();
---
<h1>Home</h1>
<p>Built at {builtAt}</p>

And one that opts in to on-demand rendering:

---
// src/pages/now.astro
export const prerender = false;

const now = new Date().toISOString();
const userAgent = Astro.request.headers.get("user-agent") ?? "unknown";
---
<h1>Rendered on demand</h1>
<p>Time: {now}</p>
<p>User agent: {userAgent}</p>

After building and starting the server, I requested each page a few times:

<h1>Home</h1><p>Built at 2026-10-24T09:45:18.690Z</p>
<h1>Rendered on demand</h1><p>Time: 2026-10-24T09:45:19.816Z</p><p>User agent: curl-test</p>
<h1>Rendered on demand</h1><p>Time: 2026-10-24T09:45:20.866Z</p><p>User agent: curl-test</p>

The static page shows the moment the build ran, and it will show that same time until the next build. The on-demand page shows a new time on every request, and it can read the request's User-Agent header, which simply doesn't exist at build time.

What the Build Produces

The difference is visible in the dist/ folder.

A Fully Static Site

Without an adapter and without any on-demand pages, astro build writes plain files:

dist/
├── index.html
├── about/index.html
├── favicon.svg
└── _astro/          (bundled CSS, JS, images)

You can upload that folder to any static host or CDN. There's no server code at all.

A Site with On-Demand Pages

With the Node adapter and one or more prerender = false pages, the build log reported:

[build] output: "static"
[build] mode: "server"
[build] adapter: @astrojs/node

The output is split in two:

dist/
├── client/
│   ├── index.html          (still prerendered)
│   └── favicon.svg
└── server/
    ├── entry.mjs           (the server entry point)
    └── chunks/
        ├── now_FLZJvguB.mjs
        ├── visits_CT6ZV7gP.mjs
        └── ...

The prerendered homepage is still a plain HTML file in dist/client/. The on-demand pages became JavaScript modules in dist/server/, which the adapter runs when a request comes in. Note the log line: output stays "static" because that's the default for pages, while mode: "server" tells you a server is part of the result.

Side-by-Side Comparison

Static (prerendered)On-demand (SSR)
When HTML is generatedOnce, at build timeOn every request
Default in AstroYesNo, opt in per page or with output: 'server'
Adapter requiredNoYes
HostingAny static host or CDNA server, serverless function or edge runtime
Data freshnessAs fresh as the last buildLive
Request headers and cookiesNot availableAvailable
Set status codes and response headersNo (host config only)Yes, via Astro.response or a Response
Dynamic routesNeed getStaticPaths()Read Astro.params directly
Time to first byteVery fast, served from cacheDepends on server work and data fetching
Cost at scaleClose to zeroCompute per request
Failure modesBuild fails, old version stays liveRuntime errors affect live requests

What Only On-Demand Rendering Can Do

Some features depend on there being a request to respond to. These only work on pages and endpoints rendered on demand:

  • Reading request data: Astro.request.headers, the request body for form posts, and query strings that differ per visitor.
  • Cookies: Astro.cookies.get() and Astro.cookies.set(), for example for sessions, preferences or a visit counter.
  • Custom responses: setting Astro.response.status or headers, returning a Response object, or calling Astro.redirect().
  • Sessions and actions that accept form submissions, which rely on a server.
  • Unbounded dynamic routes: a product page for any ID in a database, without listing every ID at build time.

Here's a page that uses several of these at once:

---
// src/pages/product/[id].astro
export const prerender = false;

const products: Record<string, { name: string; available: boolean }> = {
  "1": { name: "Desk lamp", available: true },
  "2": { name: "Old chair", available: false },
};

const product = products[Astro.params.id ?? ""];

if (!product) {
  return new Response("Not found", { status: 404 });
}

if (!product.available) {
  return Astro.redirect("/products", 301);
}

Astro.response.headers.set("Cache-Control", "public, max-age=60");
---
<h1>{product.name}</h1>

Requesting it with curl -i gave these responses:

RequestStatusNotable header
/product/1200 OKcache-control: public, max-age=60
/product/2301 Moved Permanentlylocation: /products
/product/9404 Not Foundnone

There's no getStaticPaths(): the page reads Astro.params.id at request time. A static version would need to list every product during the build, as described in the post on dynamic routes with getStaticPaths.

What Happens If You Try It on a Static Page

If a prerendered page reads request headers, Astro builds it anyway but warns you:

[WARN] `Astro.request.headers` was used when rendering the route `src/pages/static-headers.astro'`. `Astro.request.headers` is not available on prerendered pages.

The page in my test rendered with an empty value, because there was no real request during the build. Watch the build log for this warning, since it usually means a page should be rendered on demand.

What Static Rendering Does Better

On-demand rendering is more capable, but static pages win on almost every operational measure:

  • Speed: a static file can be served from a CDN edge close to the visitor, with no code running. On-demand pages pay for server work and any data fetching on every uncached request.
  • Cost: static hosting is often free or nearly free. On-demand rendering uses compute for each request.
  • Reliability: if your database or API goes down, static pages keep working. A broken build doesn't take the live site down; the previous deploy stays up.
  • Simplicity: no adapter, no runtime, no server logs to watch.

This is why the Astro docs recommend starting with the default static mode and only rendering on demand where you need it.

Mixing Both in One Project

You rarely have to choose one mode for an entire site. Astro decides per route:

  • Default (output: 'static'): every page is prerendered unless it exports prerender = false. Best when most of the site is content.
  • output: 'server': every page is rendered on demand unless it exports prerender = true. Best when most pages are personalised, such as an app behind a login.

The 'server' setting doesn't unlock any extra features; it only flips the default. A typical content site keeps the default and marks a handful of routes, such as a search results page, a dashboard or an API endpoint, as on-demand. That pattern is covered in detail in the post on mixing prerendered and on-demand pages.

There's also a middle ground at the component level. With server islands, a page stays static while one component, such as a user avatar or a personalised price, is rendered on demand and loaded after the page with server:defer. Server islands still need an adapter installed.

Making On-Demand Pages Fast

If you do render on demand, you can recover much of the static speed advantage:

  • HTML streaming: Astro streams on-demand pages in order, so the browser can start rendering the top of the page while slower components are still fetching data.
  • HTTP caching: set Cache-Control headers so a CDN can serve repeat requests without hitting your server.
  • Route caching: Astro 7 adds a stable, platform-agnostic cache config with routeRules and an Astro.cache API, backed by an in-memory provider or a CDN provider from your adapter. It's explained in the post on caching on-demand pages.

How to Choose

Ask these questions for each page:

  1. Does the HTML depend on who is asking? Logged-in state, cookies, location or headers mean on-demand.
  2. Does the data change more often than you can rebuild? Stock levels or live scores point to on-demand, or a static page with a small server island.
  3. Is the set of URLs unknown or huge? Millions of product IDs are a good fit for on-demand; a few hundred blog posts are not.
  4. Does it need to accept form posts or set cookies? That needs a server.
  5. Otherwise, prerender it.

A few examples:

PageRecommended mode
Blog post, docs page, landing pageStatic
Blog index with paginationStatic
Search results based on a query stringOn-demand
Account dashboardOn-demand
Product page with live stockStatic page plus server island, or on-demand with caching
Contact form handlerOn-demand endpoint or action
RSS feed and sitemapStatic

FAQ: Static vs On-Demand Rendering

Yes. Every page, route and endpoint is prerendered at build time unless you opt out with export const prerender = false or set output to server in your Astro config.

Yes. The Astro docs use on-demand rendering and server-side rendering to describe the same thing: HTML generated by a server at request time rather than at build time.

Yes. Without one, the build stops with a NoAdapterInstalled error. Adapters exist for Node.js, Netlify, Vercel and Cloudflare, plus community adapters for other runtimes.

Yes. Static only describes how the HTML is generated. Static pages can still include client-side scripts and hydrated framework components that run in the browser.

Yes, at build time. Top-level await in the frontmatter runs during the build, and the result is baked into the HTML until the next build.

It can. Every page is then rendered per request unless it opts back in with prerender = true. Keep fully static pages prerendered so they can be served directly from a CDN.


Conclusion

Static and on-demand rendering in Astro differ in one way that drives everything else: static pages run your code once at build time and ship finished HTML, while on-demand pages run it for every request on a server. Static pages are faster, cheaper and harder to break; on-demand pages can read cookies and headers, set status codes, redirect and show live data.

Because Astro decides per route, you don't have to pick one for the whole site. Keep the default static mode, add an adapter, and mark only the pages that truly depend on the request with export const prerender = false. For partly dynamic pages, server islands and caching let you keep most of the static performance while still showing fresh content.

Tags :
Share :

Related Posts

What are Astro Actions and how do they work?

What are Astro Actions and how do they work?

Astro Actions are type-safe backend functions that you define once on the server and then call from the browser as if they were ordinary async fu

Dive Deeper
What are Astro adapters and which one should you choose?

What are Astro adapters and which one should you choose?

An Astro adapter is an integration that builds the server part of your project for a specific runtime, such as Node.js, Vercel Functions, Netlify

Dive Deeper
How to build API endpoints in Astro?

How to build API endpoints in Astro?

To build an API endpoint in Astro, create a .js or .ts file in src/pages/ and export a function named after an HTTP method, such as GET or

Dive Deeper