How Astro's Hybrid SSG/SSR Rendering Strategy Works: Build-Time vs Request-Time Rendering

Astro blends Static Site Generation (SSG) and Server-Side Rendering (SSR) by allowing each route to opt into prerendering at build time or stay dynamic at request time, using a unified rendering engine that outputs either static HTML files or server responses.

The withastro/astro repository implements a flexible hybrid SSG/SSR rendering strategy that eliminates the traditional binary choice between static and dynamic architectures. By inspecting route-level exports and leveraging a shared rendering pipeline, Astro enables developers to deploy high-performance static pages alongside real-time server-rendered endpoints within a single codebase.

Route-Level Prerender Configuration

Astro determines whether to generate a page at build time or render it on-demand by scanning for an exported prerender constant in each route file. In packages/astro/src/core/routing/prerender.ts, the system uses the PRERENDER_REGEX to extract this flag:

const PRERENDER_REGEX = /^\s*export\s+const\s+prerender\s*=\s*(true|false);?/m;

If the export is absent, Astro falls back to the global prerender setting from astro.config.mjs via getPrerenderDefault. When a route is explicitly marked as non-prerendered, the build output shifts to server mode:

if (!route.prerender) settings.buildOutput = 'server';
  • export const prerender = true triggers static generation during the build process.
  • export const prerender = false keeps the route dynamic, requiring server-side execution at request time.

Build-Time Static Generation (SSG Pipeline)

During the astro build command, Astro instantiates the default prerenderer through createDefaultPrerenderer in packages/astro/src/core/build/default-prerenderer.ts. This Node-based prerenderer processes all routes where prerender === true.

The prerenderer imports the build entry bundle and creates a BuildApp instance. It gathers static routes—including those expanded by getStaticPaths—and invokes the rendering engine for each:

app.render(request, { routeData })

Despite using the same internal engine as runtime SSR, the prerenderer captures the output and writes the resulting HTML directly to the filesystem rather than returning an HTTP response.

Runtime Server-Side Rendering (SSR Pipeline)

For routes with prerender set to false, Astro utilizes the runtime server renderer defined in packages/astro/src/runtime/server/render/astro/render.ts. The primary entry points are renderToString, renderToReadableStream, and renderToAsyncIterable.

The rendering flow executes as follows:

  1. callComponentAsTemplateResultOrResponse generates a template result from the component.
  2. The template result's .render(destination) method streams chunks to a RenderDestination.
  3. For page routes, the renderer automatically injects the DOCTYPE declaration and buffers <head> content.
  4. The completed HTML string or streaming response is transmitted to the client.

The Unified Rendering Engine

Both SSG and SSR execution paths leverage the identical core engine located in render.ts. Whether pre-generating files during the build or responding to live HTTP requests, Astro invokes the same renderToString or streaming functions.

This architectural consistency allows developers to author components once using .astro syntax or UI framework components, while Astro optimizes the delivery method based on the route-level prerender flag. Static pages maximize CDN cacheability and eliminate compute costs, whereas dynamic pages execute per-request logic such as authentication checks or database queries.

Summary

  • Astro determines rendering mode at the route level via the exported prerender constant, detected by PRERENDER_REGEX in packages/astro/src/core/routing/prerender.ts.
  • The default prerenderer (createDefaultPrerenderer) processes static routes during astro build, writing HTML directly to disk using the same engine as runtime SSR.
  • Runtime SSR uses renderToString and streaming variants in packages/astro/src/runtime/server/render/astro/render.ts to generate responses on demand for non-prerendered routes.
  • Both strategies share a unified rendering pipeline, allowing seamless mixing of static and dynamic content without requiring different component authoring patterns.

Frequently Asked Questions

What happens if I don't export a prerender constant?

If you omit the export const prerender declaration, Astro falls back to the global prerender default specified in astro.config.mjs. The system checks this via getPrerenderDefault in the routing logic, applying the same build-time or server-mode logic based on that configuration value.

Can I use getStaticPaths with prerender = false?

No. The getStaticPaths function is designed specifically for static site generation and requires export const prerender = true to function. When prerender is false, Astro expects the route to handle dynamic parameters at request time rather than pre-generating paths during the build.

How does Astro handle streaming for SSR routes?

According to packages/astro/src/runtime/server/render/astro/render.ts, Astro supports multiple output modes including renderToReadableStream and renderToAsyncIterable. These methods create a template result that writes chunks incrementally to the RenderDestination, enabling streaming HTML responses for improved Time to First Byte (TTFB) on dynamic pages.

Is the rendering queue architecture used for .astro files?

Currently, queue-based rendering via render-async-iterable.ts is disabled for .astro files. The renderer in render.ts handles .astro components with direct string buffering or streaming approaches, while the async iterable queue system remains available for other rendering contexts within the Astro ecosystem.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →