Skip to content
Astro as routing 3 min read

Dynamic Routes

Most real sites can’t enumerate every URL by hand. Blog posts, product pages, user profiles, and docs articles all share a single template but differ by an identifier in the path. Astro solves this with dynamic routes: file names wrapped in square brackets, like [slug].astro, where the bracketed segment becomes a named parameter you read at build time. Because Astro is static-first, these routes are pre-rendered to plain HTML with zero JavaScript shipped by default — you get the ergonomics of templated routing without the runtime cost.

Bracket syntax

Any .astro page (or API route) inside src/pages/ whose file or folder name is wrapped in [ ] becomes a dynamic route. The text between the brackets is the parameter name.

src/pages/
├── blog/
│   └── [slug].astro      → /blog/:slug
├── products/
│   └── [id].astro        → /products/:id
└── [category]/
    └── [page].astro      → /:category/:page

A file like [slug].astro matches a single path segment. Folder brackets work the same way, letting you compose nested parameters such as /electronics/laptops from [category]/[page].astro.

Brackets capture exactly one segment. To match an arbitrary number of segments (e.g. /docs/a/b/c), use a rest parameter [...path].astro instead.

Reading params from Astro.params

Inside the component script (the --- fence), the captured values are available on Astro.params. Each key matches a bracket name, and each value is the matching URL segment as a string.

---
// src/pages/blog/[slug].astro
const { slug } = Astro.params;
---
<h1>Reading post: {slug}</h1>
<p>The URL segment was "{slug}".</p>

Visiting /blog/hello-world renders:

Output:

Reading post: hello-world
The URL segment was "hello-world".

getStaticPaths for static output

In Astro’s default static build, the framework needs to know which parameter values exist so it can pre-render a page for each one. You provide that list by exporting a getStaticPaths() function. It returns an array of objects, each with a params key that maps every bracket name to a concrete value.

---
// src/pages/blog/[slug].astro
export async function getStaticPaths() {
  const posts = await Astro.glob('../../content/posts/*.md');
  return posts.map((post) => ({
    params: { slug: post.frontmatter.slug },
    props: { post },
  }));
}

const { slug } = Astro.params;
const { post } = Astro.props;
const { Content } = post;
---
<article>
  <h1>{post.frontmatter.title}</h1>
  <p>Permalink: /blog/{slug}</p>
  <Content />
</article>

The props key is the companion to params: anything you place there is passed to the page via Astro.props, so you can hand over the full data record alongside the identifier and avoid a second lookup.

params vs props

It’s worth being precise about the difference, since both flow out of getStaticPaths().

ConceptSourceTypeAppears in URL?
Astro.paramsbracket segments / params keystring (or undefined)Yes
Astro.propsprops key in getStaticPathsany serializable valueNo

Params are always strings because they come from the URL. If you need a number, coerce it explicitly with Number(id).

On-demand rendering

When a route is server-rendered (a prerender = false page on an adapter, or fully on-demand output), Astro can’t enumerate paths ahead of time, so getStaticPaths() is not used. Instead, Astro.params is populated live from the incoming request URL.

---
// src/pages/products/[id].astro
export const prerender = false;

const { id } = Astro.params;
const res = await fetch(`https://api.example.com/products/${id}`);

if (!res.ok) {
  return Astro.redirect('/404');
}
const product = await res.json();
---
<h1>{product.name}</h1>
<p>${product.price}</p>

This mode trades pre-rendering for flexibility — ideal when the set of valid IDs is huge or changes constantly.

Best practices

  • Prefer static getStaticPaths() whenever the set of pages is known at build time — it ships the fastest, cheapest pages with zero JS.
  • Keep bracket names descriptive ([slug], [productId]) so Astro.params destructuring reads clearly.
  • Always coerce params before numeric or boolean logic; remember they arrive as strings.
  • Pass full records through props in getStaticPaths to avoid redundant data fetches inside the template.
  • For server-rendered dynamic routes, validate the param and redirect or return a 404 when the resource is missing.
  • Reach for rest parameters ([...path]) only when you genuinely need to match multiple, variable-length segments.
Last updated June 14, 2026
Was this helpful?