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.
| Directive | Hydrates when | Typical use |
|---|---|---|
client:load | Immediately on page load | Above-the-fold widgets |
client:idle | Browser is idle | Low-priority controls |
client:visible | Element enters viewport | Below-the-fold islands |
client:media | A media query matches | Mobile-only or desktop-only UI |
client:only | Skips SSR, renders client-side only | Browser-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.
| Trait | SSG | Server rendering |
|---|---|---|
| Rendered | At build time | Per request |
| Hosting | Static CDN, no server | Node/edge runtime needed |
| Personalization | None (same for all) | Full per-request data |
| Time to first byte | Fastest (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:visibleorclient:idleto 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.