Skip to content
Astro as rendering 4 min read

Static Site Generation

Static site generation (SSG) renders every page of your site to plain HTML during the build, before a single visitor arrives. Astro ships with SSG as its default mode, so unless you opt in to an adapter, astro build produces a folder of static files you can drop on any CDN. Because the markup is computed ahead of time and zero JavaScript is shipped by default, SSG sites are the fastest and cheapest possible way to serve content — there is no server to run, no cold start, and no per-request rendering cost.

How SSG works in Astro

When you run a build, Astro walks src/pages/, executes each page’s component script (the --- fence), resolves all data fetching, renders the resulting HTML, and writes it to dist/. Dynamic routes are expanded by getStaticPaths() into one file per path. The output is a static bundle: .html files plus hashed CSS and any assets.

npm run build

Output:

12:04:18 [build] output target: static
12:04:18 [build] Collecting build info...
12:04:19 [build] Generating static routes...
12:04:19 ▶ src/pages/index.astro
12:04:19   └─ /index.html (+8ms)
12:04:19 ▶ src/pages/blog/[slug].astro
12:04:19   ├─ /blog/hello-world/index.html (+4ms)
12:04:19   └─ /blog/astro-tips/index.html (+3ms)
12:04:20 [build] 3 page(s) built in 1.42s
12:04:20 [build] Complete!

Astro is static-first, so SSG is implied — you do not need any configuration to use it. The output option defaults to 'static':

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

export default defineConfig({
  // output: 'static' is the default — no adapter required.
});

Data at build time

In SSG, every value your page depends on must be available during the build. You fetch APIs, read files, or query a CMS directly in the component script, and the result is baked into the HTML. There is no runtime fetch on the visitor’s machine.

---
// src/pages/index.astro
const res = await fetch('https://api.example.com/stats');
const { users } = await res.json();
---
<h1>Trusted by {users.toLocaleString()} developers</h1>

For collections of pages, pair getStaticPaths() with a content collection so each entry becomes its own pre-rendered HTML file.

---
// 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.slug },
    props: { post },
  }));
}

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

Because data is frozen at build time, any change to the source content requires a rebuild and redeploy. For content that changes between deploys, trigger a build from a CMS webhook or schedule periodic builds.

Islands and interactivity

SSG does not mean a dead page. Astro’s islands architecture lets you embed framework components and hydrate only the interactive bits, leaving the rest as static HTML. The shell is pre-rendered; the island ships just enough JavaScript to come alive.

---
// src/pages/index.astro
import Counter from '../components/Counter.jsx';
---
<h1>Mostly static page</h1>
<Counter client:visible />

The client:visible directive defers hydration until the component scrolls into view, so the rest of the page costs zero JavaScript. This is what makes SSG feel dynamic without the weight of a full SPA.

DirectiveHydrates whenTypical use
client:loadImmediately on page loadAbove-the-fold widgets
client:idleBrowser is idleLow-priority controls
client:visibleElement enters viewportBelow-the-fold islands
client:mediaA media query matchesMobile-only or desktop-only UI
client:onlySkips SSR, renders client-side onlyBrowser-only libraries

When SSG is the right choice

SSG is ideal when content is the same for every visitor and changes on a predictable cadence: marketing sites, blogs, documentation, portfolios, and product catalogs. It is the wrong choice when a page must reflect per-request state — a logged-in dashboard, a live cart, or geolocated content — which is where server-side and on-demand rendering come in.

TraitSSGServer rendering
RenderedAt build timePer request
HostingStatic CDN, no serverNode/edge runtime needed
PersonalizationNone (same for all)Full per-request data
Time to first byteFastest (cached HTML)Depends on server work

Best practices

  • Keep SSG as the default and only reach for an adapter when a page genuinely needs per-request data.
  • Do all data fetching in the component script so values are baked in; never fetch the same data client-side.
  • Use content collections with getStaticPaths() to scale to hundreds of pre-rendered pages cleanly.
  • Hydrate sparingly with client:visible or client:idle to keep shipped JavaScript near zero.
  • Automate rebuilds from CMS webhooks so stale content never lingers between deploys.
  • Deploy the dist/ output to a CDN to get global edge caching for free.
Last updated June 14, 2026
Was this helpful?