How Astro File-Based Routing Handles Dynamic Routes and Parameter Extraction

Astro generates routes from the src/pages directory at build time, converting file names like [slug].astro into regular expressions that match incoming URLs and extract parameters into the Astro.params object.

Astro's file-based routing system transforms your src/pages directory into a complete routing table without manual configuration. Each file becomes a route pattern, with special bracket syntax defining dynamic segments that capture URL parameters. This article examines how the framework compiles file paths into matching RegExp patterns and extracts values using the core router implementation in the withastro/astro repository.

Converting File Names to Route Patterns

During the build process, Astro walks the src/pages directory and creates a RouteData object for every file. The file-system path is transformed into a regular expression stored in RouteData.pattern, capable of matching incoming request URLs.

Static vs Dynamic Segments

Astro distinguishes between static and dynamic route segments through file naming conventions:

  • Static routessrc/pages/about.astro generates the pattern /about
  • Dynamic segmentssrc/pages/blog/[slug].astro generates /blog/:slug and stores slug in the RouteData.params array
  • Catch-all (rest) segmentssrc/pages/[...slug].astro matches any depth using the pattern /*slug
  • Optional catch-allsrc/pages/[[...slug]].astro matches the root path plus any deeper paths

For a dynamic file like [slug].astro, the generated RegExp becomes /^\/blog\/([^/]+?)\/?$/ where the capture group extracts the parameter value.

Pattern Compilation

The route generation logic lives within Astro's compiler, which processes each page file to produce RouteData objects containing both the pattern (RegExp) and the ordered params array. This happens entirely at build time, ensuring zero runtime overhead for route discovery.

The Router Match Process

When a request arrives, Astro instantiates the Router class defined in packages/astro/src/core/routing/router.ts. The match() method executes a precise sequence to identify the correct route.

Pathname Normalization

Before pattern matching begins, the router performs several normalization steps:

  1. Adds a leading slash and removes duplicate slashes from the pathname
  2. Handles configured base path stripping
  3. Applies trailing-slash redirect logic based on configuration
  4. Adjusts for build format (directory vs file) when normalizing

Pattern Matching Logic

The router searches the sorted route list for the first entry where pattern.test(pathname) returns true. The route list is pre-sorted using routeComparator from packages/astro/src/core/routing/priority.ts to ensure deterministic matching when multiple patterns could apply.

// Conceptual flow inside Router.match()
const result = routes.find(route => route.pattern.test(pathname));
if (result) {
  return { type: 'match', route: result, params: getParams(result, pathname) };
}

Parameter Extraction with getParams

Once a route matches, Astro calls getParams from packages/astro/src/render/params-and-props.ts to extract dynamic values from the URL. This function maps RegExp capture groups to parameter names using the RouteData.params array.

export function getParams(route: RouteData, pathname: string): Params {
  const match = pathname.match(route.pattern)!;
  const params: Params = {};

  // route.params holds ordered param names from the file name
  route.params.forEach((name, i) => {
    // capture group 1 corresponds to the first param, etc.
    params[name] = match[i + 1] ?? null;
  });

  return params;
}

For [slug] patterns, the first capture group becomes params.slug. For [...slug] catch-all routes, the pattern captures the entire remainder (/^(.*)$/), storing a slash-separated string that components can split into segments. Optional catch-alls yield an empty string when nothing is captured, which Astro treats as undefined.

Route Priority and Resolution Order

Because multiple patterns can match a single URL (e.g., a static /blog/about.astro and a dynamic /blog/[slug].astro), Astro sorts routes using routeComparator in packages/astro/src/core/routing/priority.ts. The comparator applies the following precedence:

  1. Static routes take priority over dynamic routes
  2. More specific dynamic routes (fewer wildcards) rank higher than less specific ones
  3. Original file order serves as a final tiebreaker

This deterministic ordering ensures Router.match() returns the most appropriate route during the linear search through the route list.

Code Examples

Access extracted parameters inside Astro components via the global Astro.params object:

---
// src/pages/blog/[slug].astro
// Matches: /blog/hello-world
const { slug } = Astro.params;   // slug = "hello-world"
---
<h1>Blog post: {slug}</h1>

Handle catch-all routes by splitting the captured parameter into segments:

---
// src/pages/[...segments].astro
// Matches: /docs/api/config or /docs/guides/ssr
const segments = Astro.params.segments?.split('/') ?? []; 
// ['/docs', 'api', 'config'] or ['/docs', 'guides', 'ssr']
---
<nav>
  {segments.map(seg => <span>{seg}</span>)}
</nav>

For advanced use cases, you can interact with the router programmatically:

import { Router } from 'astro/src/core/routing/router';

const router = new Router(routes, {
  base: '/',
  trailingSlash: 'ignore',
  buildFormat: 'directory',
});

const result = router.match('/blog/awesome-post');
if (result.type === 'match') {
  console.log(result.params.slug); // "awesome-post"
}

Summary

  • Astro compiles src/pages files into RouteData objects at build time, transforming file names like [id].astro into RegExp patterns with capture groups.
  • The Router class in packages/astro/src/core/routing/router.ts normalizes incoming URLs and performs pattern matching against the pre-sorted route list.
  • getParams in packages/astro/src/render/params-and-props.ts extracts values from RegExp capture groups and exposes them as the Astro.params object.
  • routeComparator ensures static routes and specific dynamic patterns take precedence over catch-all routes during the matching process.

Frequently Asked Questions

How does Astro differentiate between static and dynamic routes?

Astro examines file names inside src/pages for bracket syntax. Files without brackets generate static routes, while files containing [param] or [...param] generate dynamic patterns with corresponding regular expressions. The route generation logic stores parameter names in the RouteData.params array to map capture groups during request handling.

What happens when multiple routes match the same URL?

When multiple patterns match, Astro applies routeComparator from packages/astro/src/core/routing/priority.ts to determine precedence. Static routes always win over dynamic ones, and routes with fewer wildcards beat more generic catch-all patterns. The router returns the first match from this sorted list, ensuring predictable behavior for overlapping definitions.

How are rest parameters ([...slug]) different from optional catch-alls ([[...slug]])?

Rest parameters [...slug] require at least one path segment and capture everything remaining in the URL as a single string. Optional catch-alls [[...slug]] use a modified pattern that also matches the root path with no segments, yielding an empty string or undefined for the parameter when the URL contains no additional path parts.

Where does parameter extraction occur in the request lifecycle?

Parameter extraction happens inside the router's match() method immediately after pattern validation succeeds. The getParams function executes pathname.match(route.pattern) and iterates through capture groups, assigning values to keys defined in route.params. This occurs before the page component renders, making params available immediately in the frontmatter script.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →