Skip to content
Astro as rendering 4 min read

Rendering Modes

Astro lets you decide where and when each page is built: ahead of time as static HTML, on every request on a server, or a mix of both within the same project. This single decision shapes performance, hosting cost, and which dynamic features you can use. Because Astro ships zero JavaScript by default and renders to HTML regardless of mode, even your most dynamic pages stay fast. Understanding the rendering modes is the foundation for everything else in this section.

The three rendering strategies

Astro supports three ways to render a page:

  • Static site generation (SSG) — pages are rendered to HTML at build time and served as flat files from a CDN. This is the default and the fastest option.
  • On-demand server rendering (SSR) — pages are rendered per request by a server or serverless function, enabling personalization, fresh data, and access to the request object.
  • Hybrid rendering — a single project ships mostly static pages but opts specific routes into on-demand rendering. This is the recommended approach for most real-world sites.

The mode is controlled by the output option in astro.config.mjs, combined with the export const prerender flag on individual pages.

Configuring the output option

In modern Astro (4.x and 5.x) there are two values for output:

output valueDefault behaviorPer-route override
'static' (default)All pages prerendered at build timeexport const prerender = false makes a route on-demand
'server'All pages rendered on-demandexport const prerender = true makes a route static

Astro 5 simplified the older three-value API. The legacy 'hybrid' value was removed — “hybrid” is now just output: 'static' (or 'server') with per-route prerender overrides. Both static and server modes support mixing.

To render any page on demand, you need a server adapter. Add one with the CLI:

npx astro add node

This installs the adapter and updates your config:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';

export default defineConfig({
  output: 'static',
  adapter: node({ mode: 'standalone' }),
});

With output: 'static', pages are prerendered unless you opt them out. Adapters for @astrojs/vercel, @astrojs/netlify, and @astrojs/cloudflare work the same way for serverless and edge platforms.

Choosing per-route with prerender

The real power is mixing modes inside one project. Keep marketing and blog pages static, and make the dashboard or cart on-demand. Each page declares its own intent with an exported prerender constant.

A static page (the default under output: 'static') needs nothing extra:

---
// src/pages/blog/[slug].astro
import { getCollection } from 'astro:content';

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map((post) => ({
    params: { slug: post.id },
    props: { post },
  }));
}

const { post } = Astro.props;
const { Content } = await post.render();
---
<article>
  <h1>{post.data.title}</h1>
  <Content />
</article>

An on-demand page opts out of prerendering and can read the live request:

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

const user = Astro.request.headers.get('cookie') ?? '';
const isLoggedIn = user.includes('session=');
---
<main>
  {isLoggedIn ? <p>Welcome back!</p> : <a href="/login">Sign in</a>}
</main>

When you run the build, Astro reports exactly what it produced:

Output:

▶ src/pages/blog/[slug].astro
  └─ /blog/hello-world/index.html (+12ms)
  └─ /blog/astro-tips/index.html (+9ms)
λ src/pages/account.astro (prerender: false)

The marks prerendered routes and the λ marks on-demand (lambda) routes, so you can confirm each page renders the way you intended.

Islands work in every mode

Rendering mode controls the page HTML; client-side interactivity is separate and handled by islands. A component hydrates only when you add a client:* directive, regardless of whether the surrounding page is static or on-demand.

---
import Counter from '../components/Counter.jsx';
---
<!-- Static HTML page, single interactive island -->
<Counter client:visible />

The rest of the page stays pure HTML with zero JavaScript shipped. This is why Astro stays fast even when a page is rendered on demand — only the islands you explicitly mark become JavaScript.

When to use each mode

Use caseRecommended mode
Blog, docs, marketingStatic (SSG)
Personalized dashboardsOn-demand (prerender = false)
Auth-gated pages, formsOn-demand
E-commerce with dynamic pricingHybrid (static catalog + on-demand cart)
API endpointsOn-demand

Best practices

  • Default to output: 'static' and opt individual routes into on-demand rendering only when they truly need per-request data.
  • Add the adapter that matches your host (Node, Vercel, Netlify, Cloudflare) before relying on any on-demand route, or the build will fail.
  • Keep export const prerender declarations explicit on dynamic pages so the rendering intent is obvious to future maintainers.
  • Use islands and client:* directives for interactivity instead of switching a whole page to on-demand rendering.
  • Prefer client:visible or client:idle over client:load to defer hydration and protect Core Web Vitals.
  • Verify the build output ( vs λ) to confirm each route renders in the mode you expect before deploying.
Last updated June 14, 2026
Was this helpful?