Routing

Purpose and Scope

Astro routing is the system that turns a project’s page files, injected routes, default routes, redirects, and runtime matches into the URLs a site can serve. For application authors, the public model is file-based: supported page files under src/pages/ create routes, static filenames create static URLs, and bracketed filenames create dynamic parameters. For contributors, the routing core has to preserve that simple authoring model while producing a precise manifest that build, dev, server rendering, prerendering, integrations, and error handling can all consume consistently.

The files covered here sit in Astro’s core routing layer. They do not define every user-facing page convention by themselves, but they show the important internal contracts behind those conventions: route manifest creation, route part parsing, URL generation from params, redirect fallback markup, default 404 and server-island routes, and development-time route matching. Read this page when you need to understand why two dynamic routes conflict, how a missing getStaticPaths() result behaves in dev, how generated paths encode special characters, or where framework-provided routes enter the manifest.

Sources: packages/astro/src/core/routing/create-manifest.ts, packages/astro/src/core/routing/generator.ts, packages/astro/src/core/routing/dev.ts

Relevant Source Files

  • packages/astro/src/core/routing/create-manifest.ts builds route data from project pages, injected routes, redirects, image and server-island endpoints, prerender settings, route patterns, route sorting, and integration hooks.
  • packages/astro/src/core/routing/generator.ts converts parsed route segments plus route params into concrete path strings, including dynamic, rest, encoded, and trailing-slash behavior.
  • packages/astro/src/core/routing/dev.ts matches incoming development-server URLs against the routes list, validates dynamic static paths through props resolution, retries alternative .html forms, and falls back to custom 404 routes.
  • packages/astro/src/core/routing/3xx.ts renders the minimal HTML page used for SSR redirects when a response body is needed for redirect navigation and crawlers.
  • packages/astro/src/core/routing/astro-designed-error-pages.ts ensures the manifest contains Astro’s designed 404 route when the project does not define its own /404.
  • packages/astro/src/core/routing/default.ts creates default runtime route entries for the built-in 404 component and the server-island endpoint.

System-to-Code Mapping

The manifest builder is the center of the routing system. create-manifest.ts imports page resolution, markdown extension support, route priority comparison, route pattern generation, segment validation, prerender option detection, integration hook execution, redirect errors, image endpoint injection, and server-island route injection. That collection of dependencies shows the manifest’s job: it is not only a list of files. It is the normalized routing contract that downstream build and runtime code can use without re-discovering project structure.

Dynamic route parsing starts with route parts. In create-manifest.ts, getParts() splits a filename segment on bracketed parameters and marks each RoutePart as static or dynamic, with spread set for rest parameters such as [...slug]. Parameter names are validated against an identifier-like pattern, and invalid route filenames throw immediately. The same file also defines semantic segment comparison, where /[bar] and /[baz] are treated as equivalent because they would match the same URLs, while static text must match exactly.

Sources: packages/astro/src/core/routing/create-manifest.ts

Static, Dynamic, and Generated Paths

A route can be understood as parsed segments plus configuration. Static segments contribute literal text. Dynamic segments read a named value from params. Spread segments read the name after the leading ... and may become an empty string. generator.ts is the source-level reference for turning that structure into a URL path. It normalizes string params, URL-encodes # and ?, preserves literal square brackets that were encoded as %5B or %5D, collapses duplicate leading slashes, and returns / when the generated path would otherwise be empty.

The generator enforces an important boundary between ordinary dynamic params and rest params. For a normal dynamic part, a missing parameter throws TypeError: Missing parameter: name, because there is no valid URL for a required segment. For a spread parameter, a missing value becomes an empty string, which lets a catch-all route represent both a parent route and nested paths. Trailing slash behavior is applied after segment generation: only trailingSlash: 'always' and a non-empty segment list automatically append /.

Compact route generation reference:

  • getRouteGenerator(segments, addTrailingSlash) returns a function that accepts a params object and returns a path string.
  • sanitizeParams(params) normalizes string values and encodes # as %23 and ? as %3F.
  • getParameter(part, params) resolves static, dynamic, and spread route parts, throwing for missing required dynamic params.
  • getSegment(segment, params) joins route parts and prefixes the segment with / when it has content.

Sources: packages/astro/src/core/routing/generator.ts

Redirects and Error Routes

Redirect handling has a small but visible routing surface. 3xx.ts exports redirectTemplate(), which creates the minimal HTML document used for SSR redirects. The template includes a refresh meta tag, robots noindex, a canonical link to the absolute destination, and an anchor pointing to the relative destination. It escapes the rendered URLs and optional source route, which matters because redirects can be configured from route-level or integration-level data and may include characters that should not become raw HTML.

The redirect template also encodes status-specific crawler behavior. A 302 redirect uses a short refresh delay, while other statuses use an immediate refresh. That mirrors the comment in the implementation about search-engine interpretation of temporary redirects. Separately, create-manifest.ts imports redirect-specific error definitions such as InvalidRedirectDestination and UnsupportedExternalRedirect, which indicates that redirect validation is part of manifest construction rather than only runtime response handling.

Error routes are treated as routes too. astro-designed-error-pages.ts exposes ensure404Route(manifest), which appends DEFAULT_404_ROUTE when no route with route === '/404' exists. default.ts then maps default components into runtime route params, including the default 404 component and the server-island component. This split keeps the manifest complete while also giving SSR code concrete component instances and component matching logic for framework-owned routes.

Sources: packages/astro/src/core/routing/3xx.ts, packages/astro/src/core/routing/astro-designed-error-pages.ts, packages/astro/src/core/routing/default.ts

Development-Time Matching Flow

Development routing has to feel like production while remaining forgiving enough to surface useful errors. dev.ts exports matchRoute(pathname, routesList, pipeline, manifest). It first calls matchAllRoutes() to find candidate route data, then asks prerender routing for sorted preloaded matches. Each candidate is validated by loading the component module and calling getProps() with route data, the route cache, the request pathname, server-like mode, base path, and trailing slash settings. A candidate only wins after its params and static paths can actually produce props for that pathname.

The retry behavior is deliberate. If getProps() throws Astro’s NoMatchingStaticPathFound, the matcher ignores that candidate and continues, because another route pattern may still be valid. If a candidate throws a different error, the matcher saves the first non-routing error and continues checking the remaining candidates. Only after all candidates fail does it rethrow that saved error. This prevents one broken route from hiding a different matching route, while still ensuring real user code errors are not silently converted into 404s.

The dev matcher also normalizes common production URL forms. If no candidate works, it retries after replacing /index.html with / or removing a trailing .html. That makes development behavior closer to static build output where both file and directory formats may appear. If candidate patterns existed but none had matching static paths, the router logs a warning with NoMatchingStaticPathFound and a hint listing possible route components. Finally, it resolves an i18n-aware 404 route path, prefers that route, falls back to a custom 404, and returns undefined only when no error route can handle the request.

Sources: packages/astro/src/core/routing/dev.ts

Implementation Details and Contributor Notes

Route ordering and route equivalence are where many routing changes become risky. The manifest layer imports routeComparator, getPattern(), and validateSegment(), and it defines isSemanticallyEqualSegment() to compare the shape of segments independent of dynamic parameter names. This means that renaming [id] to [slug] does not create a different match shape; only static text, segment length, dynamic-ness, and spread-ness affect semantic equality. Contributors changing parser behavior should think in terms of matched URL sets, not only filenames.

Injected framework routes are also part of routing, not a separate escape hatch. create-manifest.ts imports injectImageEndpoint() and injectServerIslandRoute(), while default.ts includes SERVER_ISLAND_ROUTE and SERVER_ISLAND_COMPONENT in default route creation. That means core features such as image services and server islands need to coexist with user pages, redirects, custom 404s, prerender settings, and integration hooks in one manifest. A routing change should be tested against both ordinary src/pages/ files and these framework-owned routes.

When debugging, start with the layer that owns the symptom. Filename parsing, duplicate dynamic shapes, redirect validity, injected endpoints, and prerender defaults point toward manifest construction. Missing params, encoded characters, or unexpected trailing slashes point toward the route generator. A page that works in build but not dev, or vice versa, points toward matchRoute() and its static-path validation flow. A missing custom 404 or default 404 behavior points toward ensure404Route() and default route creation.

Next Steps

To apply this routing model in an Astro project, begin with the public conventions: place page files under src/pages/, use ordinary <a> elements for navigation, use bracketed filenames for dynamic params, and export getStaticPaths() when a static build must enumerate dynamic pages. When you need to reason about core behavior, inspect the route manifest first, then generated paths, then dev matching. Related pages: astro-pages-routing-basics, endpoints, middleware, server-side-rendering-adapters, and configuration-reference.