File-Based Routing & Dynamic Routes

Chapter 3
File-Based Routing & Dynamic Routes
src/pages/, [slug].astro, [...path].astro, and getStaticPaths()

Chapter 1 briefly showed a plain static file in src/pages/ becoming a real route. This chapter covers the rest of the picture: dynamic segments, catch-all routes for arbitrary-depth paths, and the one real constraint every dynamic route runs into by default — getStaticPaths().

Static Routes, Recapped

  • src/pages/index.astro/
  • src/pages/about.astro/about
  • src/pages/blog/index.astro/blog

Folders nest naturally into path segments — no route configuration file exists anywhere in an Astro project.

Dynamic Single-Segment Routes

// src/pages/blog/[slug].astro --- export async function getStaticPaths() { return [ { params: { slug: 'first-post' } }, { params: { slug: 'second-post' } }, ]; } const { slug } = Astro.params; --- <h1>{slug}</h1>

[slug].astro matches any single path segment at that position — /blog/first-post, /blog/second-post — captured into Astro.params.slug.

Why getStaticPaths() is required here — a real constraint, not a formality
Astro's default output mode is fully static: the entire site is pre-rendered to plain HTML files at build time, with no server running afterward to resolve a route on demand. That means Astro has to know, at build time, every single value the dynamic segment could ever take — getStaticPaths() is exactly that declaration. Visit a URL whose value wasn't returned by getStaticPaths(), and there's simply no pre-built file for it — a real 404, not a route that resolves lazily. Chapter 10's server output mode removes this constraint entirely, resolving dynamic routes per-request instead.

Catch-All Routes: Arbitrary-Depth Paths

// src/pages/docs/[...path].astro --- export async function getStaticPaths() { return [ { params: { path: 'guides/getting-started' } }, { params: { path: 'reference/config' } }, ]; } const { path } = Astro.params; --- <h1>{path}</h1>

[...path].astro is Astro's own catch-all segment — the three dots match zero or more path segments, slashes included, all captured into a single string on Astro.params.path. Same requirement applies: every real deep path this route should serve has to appear in getStaticPaths()'s own returned list.

Deliberate groundwork for a future rebuild course
This exact pattern — a catch-all segment resolving an arbitrary-depth path — is the same problem the site's own Website Rebuild series solved once per framework: Next.js's own [...path] folder convention, Django's <path:full_path> converter, Laravel's {path?} plus a regex constraint, and Rails' own *path glob. Astro's [...path].astro is this course's own version of the identical idea — the piece a future Website Rebuild with Astro course would build directly on.

No Client-Side Router by Default

Unlike a single-page application framework, Astro doesn't ship a client-side router at all by default — navigating between pages is a genuine full page load, since there's no persistent client-side app to route within. An optional View Transitions API exists for smoother navigation animations, but it's a later, opt-in refinement, not a default routing mechanism the way React Router or Vue Router are.

Coding Challenges

Challenge 1

Build a dynamic route at src/pages/products/[id].astro, with getStaticPaths() returning at least 3 hard-coded IDs, rendering the current ID via Astro.params.

📄 View solution
Challenge 2

Build a catch-all route at src/pages/docs/[...path].astro, with getStaticPaths() covering at least one multi-segment path, rendering the full captured path.

📄 View solution
Challenge 3

Build the site (npm run build) and confirm a URL not covered by getStaticPaths() has no corresponding output file — demonstrating why every real path has to be declared up front in static mode.

📄 View solution

Chapter 3 Quick Reference

  • src/pages/ — every file becomes a real route, folders nest into path segments
  • [slug].astro — matches one dynamic path segment, captured via Astro.params
  • [...path].astro — catch-all, matches an arbitrary-depth path in one segment string
  • getStaticPaths() — required in static output mode; every real path must be declared up front, or it's a genuine 404
  • No client-side router by default — navigation is a real page load unless View Transitions is added
  • Next chapter: Layouts, Slots & Component Composition