getStaticPaths
When a route file contains a dynamic segment like [slug], Astro has no way of knowing which concrete URLs to build at compile time. In static (SSG) mode you answer that question explicitly by exporting a getStaticPaths() function that returns every path you want pre-rendered. Astro runs it once at build time, generates one HTML file per entry, and ships zero JavaScript by default. This is the backbone of fast, fully static content sites built on dynamic route templates.
Where getStaticPaths runs
getStaticPaths() is a special async function you export from the component script (the --- fence) of a dynamic page in src/pages/. It executes only on the server at build time — never in the browser and never per-request. Because of that you can freely call databases, read the filesystem, or hit APIs inside it; none of that code or its dependencies reach the client.
getStaticPaths is required for any dynamic route in static-rendered pages. If you forget it, Astro throws a build error telling you the route is missing static paths.
The return shape: params and props
The function must return an array. Each item is an object with a params key (and optionally a props key). The params object provides the values that fill the bracketed segments in the filename — its keys must match the segment names exactly. The props object passes arbitrary data to that page, letting you avoid re-fetching inside the component.
---
// src/pages/blog/[slug].astro
export async function getStaticPaths() {
const posts = await fetch("https://api.example.com/posts").then((r) =>
r.json(),
);
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
---
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
</article>
You read the matched segment via Astro.params and the passed data via Astro.props. With the snippet above, a posts list of three entries produces three HTML files at build time.
Output:
dist/blog/getting-started/index.html
dist/blog/astro-islands/index.html
dist/blog/zero-js-by-default/index.html
params vs props
It is worth being precise about what belongs where, because mixing them up is the most common mistake.
| Concern | params | props |
|---|---|---|
| Purpose | Fills the URL segment(s) | Passes data to the page |
| Affects the generated URL | Yes | No |
| Allowed value types | Strings only | Any serializable value |
| Accessed via | Astro.params | Astro.props |
| Required | Yes | No |
Values inside
paramsare coerced to strings in the URL, so anidof42becomes/products/42. Keep that in mind when comparing —Astro.params.idis the string"42", not the number.
Pairing with content collections
The idiomatic source of paths for a blog or docs site is a content collection. Astro ships a helper that fetches the entries; you map each one to a path and pass the entry as a prop.
---
// src/pages/docs/[...slug].astro
import { getCollection } from "astro:content";
export async function getStaticPaths() {
const entries = await getCollection("docs");
return entries.map((entry) => ({
params: { slug: entry.slug },
props: { entry },
}));
}
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<Content />
Because getStaticPaths runs at build time, the Markdown is parsed and rendered once, and the page ships as static HTML with no runtime cost.
Multiple dynamic segments
If a filename has more than one bracket — for example [lang]/[slug].astro — every entry’s params must include a key for each segment. Any path whose params does not cover all segments is skipped.
---
// src/pages/[lang]/[slug].astro
export async function getStaticPaths() {
const langs = ["en", "fr"];
const slugs = ["home", "about"];
return langs.flatMap((lang) =>
slugs.map((slug) => ({
params: { lang, slug },
props: { lang, slug },
})),
);
}
const { lang, slug } = Astro.params;
---
<p>Locale: {lang} — Page: {slug}</p>
SSR mode and prerender
In a project configured for on-demand rendering (SSR), dynamic routes resolve params at request time and do not require getStaticPaths. To statically pre-render a single page inside an otherwise-SSR project, opt that page back into SSG and keep getStaticPaths:
export const prerender = true;
export async function getStaticPaths() {
return [{ params: { slug: "welcome" } }];
}
Best practices
- Keep all data fetching for a route inside
getStaticPathsand hand results to the page throughpropsso you never fetch twice. - Match
paramskeys to the bracket names exactly; a typo silently drops the path rather than erroring. - Return strings in
params— numbers and booleans should be converted before they reach the URL. - Prefer content collections over raw
fetchfor local Markdown so you get type safety and schema validation for free. - Generate only the paths you actually need; thousands of build-time pages slow the build, so paginate or filter when a collection is large.
- Use
[...slug]rest parameters when your slugs contain nested paths, and split a singleslugstring into the segments your layout expects.