Rendering
Astro gives you fine-grained control over how each route renders: static at build time, or on-demand per request. You set this per-route, not globally.
Output modes
output: 'static' (default)
Every page is pre-rendered to HTML at build time. This is the default and the fastest option for content sites.
// astro.config.mjs
export default defineConfig({
output: 'static', // default, can omit
});
To opt specific routes into on-demand rendering:
---
// src/pages/dashboard.astro
export const prerender = false; // this route renders on each request
---
This requires an adapter (Node, Cloudflare, etc.) for the on-demand routes.
output: 'server'
All routes render on-demand by default. Opt specific routes into static prerendering:
---
// src/pages/about.astro
export const prerender = true; // prerender this route at build time
---
No more output: 'hybrid'
Astro 5 removed hybrid mode. The current model is simpler:
static(default) +prerender = falseon specific routesserver+prerender = trueon specific routes
Both give you the same flexibility. The difference is just which is the default.
Adapters
On-demand rendering requires an adapter that tells Astro how to run on your host:
npx astro add node # Node.js server
npx astro add cloudflare # Cloudflare Workers/Pages
npx astro add vercel # Vercel Functions
npx astro add netlify # Netlify Functions
// astro.config.mjs
import node from '@astrojs/node';
export default defineConfig({
output: 'static',
adapter: node({ mode: 'standalone' }),
});
Even with output: 'static', you need an adapter if any route uses prerender = false.
When to use each mode
| Scenario | Rendering | Why |
|---|---|---|
| Blog post, docs page | Static (prerender) | Content rarely changes, fastest possible |
| News story page | On-demand | Content changes frequently, needs fresh data |
| RSS feed endpoint | Static or on-demand | Depends on update frequency |
| Search results page | On-demand | Depends on query params |
| About page | Static | Rarely changes |
| User dashboard | On-demand | Personalized content |
Cache-Control headers
For on-demand routes, you control caching explicitly with HTTP headers:
---
export const prerender = false;
const data = await fetch('https://api.example.com/story/123').then(r => r.json());
// Set cache headers
Astro.response.headers.set('Cache-Control', 'public, max-age=300, s-maxage=600');
---
<h1>{data.title}</h1>
This is more transparent than Next.js’s cache behavior. You set HTTP headers directly rather than relying on framework-level caching abstractions.
Experimental route caching (Astro 6)
Astro 6 introduced experimental route caching with tag-based and path-based invalidation:
// astro.config.mjs
export default defineConfig({
experimental: {
routeCache: true,
},
});
This is still experimental as of Astro 6.1. For production use, explicit Cache-Control headers with CDN caching is the safer choice.
Comparison with Next.js rendering
| Concept | Next.js | Astro |
|---|---|---|
| Default | Server Components (on-demand) | Static (prerendered) |
| Static opt-in | Pages are static by default in App Router with no dynamic APIs | export const prerender = true |
| Dynamic opt-in | Dynamic APIs (cookies(), headers(), searchParams) | export const prerender = false |
| Revalidation | revalidate, revalidateTag(), revalidatePath() | HTTP Cache-Control headers (or experimental route caching) |
| ISR | Built into Next.js | Use CDN cache + stale-while-revalidate headers |
| Streaming | React Suspense | Not applicable (no React tree to stream) |