Skip to content
Astro as routing 4 min read

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.

Concernparamsprops
PurposeFills the URL segment(s)Passes data to the page
Affects the generated URLYesNo
Allowed value typesStrings onlyAny serializable value
Accessed viaAstro.paramsAstro.props
RequiredYesNo

Values inside params are coerced to strings in the URL, so an id of 42 becomes /products/42. Keep that in mind when comparing — Astro.params.id is 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 getStaticPaths and hand results to the page through props so you never fetch twice.
  • Match params keys 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 fetch for 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 single slug string into the segments your layout expects.
Last updated June 14, 2026
Was this helpful?