---
title: "Route Matching"
description: "Route matching in Next.js is the foundational mechanism responsible for mapping incoming HTTP requests or client-side navigation paths to the correct App Router (app/) or Pages Router (pages/) exec..."
last_updated: "2026-09-23T10:52:03.111005+00:00"
canonical_url: "https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/route-matching"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [packages/next/src/server/base-server.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/base-server.ts)
- [packages/next/src/server/config-shared.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/config-shared.ts)
- [packages/next/src/server/next-server.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/next-server.ts)
- [packages/next/src/server/route-matchers/pages-route-matcher.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matchers/pages-route-matcher.ts)
- [packages/next-routing/src/resolve-routes.ts](https://github.com/blade47/next-routing/src/resolve-routes.ts)
- [packages/next/src/server/api-utils/node/api-resolver.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/api-utils/node/api-resolver.ts)
- [packages/next/src/server/lib/router-server.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/router-server.ts)
- [packages/next/src/server/route-matcher-managers/default-route-matcher-manager.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matcher-managers/default-route-matcher-manager.ts)
- [packages/next/src/server/route-matchers/app-page-route-matcher.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matchers/app-page-route-matcher.ts)
- [packages/next/src/client/link.tsx](https://github.com/blade47/next.js/blob/main/packages/next/src/client/link.tsx)
- [packages/next/errors.json](https://github.com/blade47/next.js/blob/main/packages/next/errors.json)
- [packages/next/src/shared/lib/router/router.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/router/router.ts)
- [packages/next/src/server/route-matcher-managers/route-matcher-manager.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matcher-managers/route-matcher-manager.ts)
- [packages/next/src/server/lib/router-utils/typegen.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/router-utils/typegen.ts)
- [packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts)
- [packages/next/src/server/route-matchers/app-route-route-matcher.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matchers/app-route-route-matcher.ts)
- [packages/next/src/server/route-matchers/route-matcher.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matchers/route-matcher.ts)
- [packages/next/src/server/request-meta.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/request-meta.ts)
- [packages/next/src/server/route-modules/pages/pages-handler.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/pages/pages-handler.ts)
- [packages/next/src/server/route-matchers/pages-api-route-matcher.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matchers/pages-api-route-matcher.ts)
- [packages/next/src/server/route-modules/app-page/helpers/prerender-manifest-matcher.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-page/helpers/prerender-manifest-matcher.ts)
- [packages/next/src/server/lib/dev-bundler-service.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/dev-bundler-service.ts)
- [packages/next/src/server/route-matcher-providers/pages-route-matcher-provider.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matcher-providers/pages-route-matcher-provider.ts)
- [packages/next/src/server/route-modules/app-page/module.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-page/module.ts)
- [packages/next/src/client/image-component.tsx](https://github.com/blade47/next.js/blob/main/packages/next/src/client/image-component.tsx)
- [packages/next/src/server/route-matcher-providers/app-route-route-matcher-provider.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matcher-providers/app-route-route-matcher-provider.ts)
- [packages/next/src/shared/lib/router/utils/interception-routes.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/shared/lib/router/utils/interception-routes.ts)
- [packages/next/src/server/route-matcher-providers/app-page-route-matcher-provider.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matcher-providers/app-page-route-matcher-provider.ts)
- [packages/next/src/server/route-matchers/locale-route-matcher.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matchers/locale-route-matcher.ts)
- [packages/next/src/client/components/segment-cache/optimistic-routes.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts)
</details>

## Overview

### Overview
Route matching in Next.js is the foundational mechanism responsible for mapping incoming HTTP requests or client-side navigation paths to the correct App Router (`app/`) or Pages Router (`pages/`) execution handler. When a request hits the server or a client initiates a transition, Next.js must resolve file-system routes, dynamic slugs, internationalized locales, and API endpoints without ambiguity. The subsystem solves this through an extensible architecture composed of route matcher providers, individual route matchers, route pattern normalizers, and the `DefaultRouteMatcherManager`.

Sources: [packages/next/src/server/base-server.ts:802-848](https://github.com/blade47/next.js/blob/main/packages/next/src/server/base-server.ts#L802-L848)

The design separates manifest loading and route pattern transformation from the active lookup engine. Matchers are divided into static and dynamic collections, with dynamic routes sorted deterministically using specificity rules. Furthermore, client-side optimistic route prediction uses a trie-based structure to bypass server round-trips for known paths. This wiki page details the architecture, request pipelines, execution walkthroughs, and trade-offs of the Next.js route matching subsystem.

Sources: [packages/next/src/server/route-matcher-managers/default-route-matcher-manager.ts:19-292](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matcher-managers/default-route-matcher-manager.ts#L19-L292)

---

## Route Matcher Providers and Manifest Ingestion

The route matching subsystem relies on route matcher providers to ingest build-time manifests (such as `pages-manifest.json` and `app-paths-manifest.json`) and transform them into concrete `RouteMatcher` instances. The base server configures a `DefaultRouteMatcherManager` by pushing provider instances through `BaseServer.getRouteMatchers()`.

Sources: [packages/next/src/server/base-server.ts:802-848](https://github.com/blade47/next.js/blob/main/packages/next/src/server/base-server.ts#L802-L848)

The supported provider classes and their corresponding manifests and route kinds include:

| Provider Class | Target Manifest | Route Kind | Description |
| :--- | :--- | :--- | :--- |
| `PagesRouteMatcherProvider` | `pages-manifest.json` | `RouteKind.PAGES` | Matches non-API page components under `pages/` |
| `PagesAPIRouteMatcherProvider` | `pages-manifest.json` | `RouteKind.PAGES_API` | Matches API route handlers under `pages/api/` |
| `AppPageRouteMatcherProvider` | `app-paths-manifest.json` | `RouteKind.APP_PAGE` | Matches React Server Component pages under `app/` |
| `AppRouteRouteMatcherProvider` | `app-paths-manifest.json` | `RouteKind.APP_ROUTE` | Matches App Router Route Handlers (`route.ts`) |

Sources: [packages/next/src/server/route-matcher-providers/pages-route-matcher-provider.ts:16-82](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matcher-providers/pages-route-matcher-provider.ts#L16-L82), [packages/next/src/server/route-matcher-providers/app-page-route-matcher-provider.ts:13-60](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matcher-providers/app-page-route-matcher-provider.ts#L13-L60), [packages/next/src/server/route-matcher-providers/app-route-route-matcher-provider.ts:12-47](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matcher-providers/app-route-route-matcher-provider.ts#L12-L47)

---

## Core Matcher Hierarchy and Execution

At the core of individual route matching is the `RouteMatcher` class, which wraps a `RouteDefinition` and optionally compiles a dynamic regular expression matcher if the pathname contains dynamic route segments. Specialized subclasses like `LocaleRouteMatcher`, `PagesRouteMatcher`, and `AppPageRouteMatcher` extend this behavior to handle internationalization or specific route metadata.

Sources: [packages/next/src/server/route-matchers/route-matcher.ts:16-66](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matchers/route-matcher.ts#L16-L66)

```mermaid
classDiagram
    class RouteMatcher {
        +D definition
        +Array~RouteMatcher~ duplicated
        +get identity()
        +get isDynamic()
        +match(pathname)
        +test(pathname)
    }
    class LocaleRouteMatcher {
        +get identity()
        +match(pathname, options)
        +test(pathname, options)
    }
    class PagesRouteMatcher {
    }
    class AppPageRouteMatcher {
        +get identity()
    }
    class AppRouteRouteMatcher {
    }

    RouteMatcher <|-- LocaleRouteMatcher
    RouteMatcher <|-- PagesRouteMatcher
    RouteMatcher <|-- AppPageRouteMatcher
    RouteMatcher <|-- AppRouteRouteMatcher
    LocaleRouteMatcher <|-- PagesLocaleRouteMatcher
    LocaleRouteMatcher <|-- PagesAPILocaleRouteMatcher
```

Sources: [packages/next/src/server/route-matchers/route-matcher.ts:16-66](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matchers/route-matcher.ts#L16-L66), [packages/next/src/server/route-matchers/locale-route-matcher.ts:15-85](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matchers/locale-route-matcher.ts#L15-L85)

When `RouteMatcher.test(pathname)` is invoked, it checks whether the matcher is dynamic. If `this.dynamic` is defined, it executes the compiled `RouteMatchFn` to extract route parameters; otherwise, it performs an exact string comparison:
```typescript
  public test(pathname: string): RouteMatchResult | null {
    if (this.dynamic) {
      const params = this.dynamic(pathname)
      if (!params) return null

      return { params }
    }

    if (pathname === this.definition.pathname) {
      return {}
    }

    return null
  }
```
Sources: [packages/next/src/server/route-matchers/route-matcher.ts:52-65](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matchers/route-matcher.ts#L52-L65)

---

## RouteMatcherManager and Matching Order

The `DefaultRouteMatcherManager` orchestrates multiple providers and manages collections of static and dynamic matchers. During initialization or reload, it sorts dynamic routes using `getSortedRoutes` to ensure that specific static segments take precedence over dynamic slugs and catch-all routes.

Sources: [packages/next/src/server/route-matcher-managers/default-route-matcher-manager.ts:19-173](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matcher-managers/default-route-matcher-manager.ts#L19-L173)

```mermaid
flowchart TD
    A["Incoming Request Pathname"] --> B{"Is Dynamic Route?"}
    B -->|No| C["Iterate matchers.static"]
    C -->|Match Found| D["Yield Match"]
    C -->|No Match| E{"options.skipDynamic?"}
    B -->|Yes| F["Iterate matchers.dynamic (Sorted)"]
    E -->|True| G["Return null"]
    E -->|False| F
    F -->|Match Found| D
    F -->|No Match| G
```

Sources: [packages/next/src/server/route-matcher-managers/default-route-matcher-manager.ts:245-291](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matcher-managers/default-route-matcher-manager.ts#L245-L291)

> [!WARNING]
> If a match is attempted before route compilation finishes (`this.lastCompilationID !== this.compilationID`), the manager throws an invariant error: `'Invariant: expected routes to have been loaded before match'`. This guards against requests racing route initialization.

Sources: [packages/next/src/server/route-matcher-managers/default-route-matcher-manager.ts:255-259](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matcher-managers/default-route-matcher-manager.ts#L255-L259)

---

## Call-Chain Execution Walkthrough

When Next.js processes an incoming request inside `NextServer.handleRequest` (or the router server), route matching follows a precise, multi-step pipeline starting from URL normalization and locale analysis down to matcher evaluation and meta attachment.

Sources: [packages/next/src/server/next-server.ts:1102-1120](https://github.com/blade47/next.js/blob/main/packages/next/src/server/next-server.ts#L1102-L1120)

The call chain proceeds as follows:
1. **Request Normalization (`BaseServer` / `AppPageRouteModule.normalizeUrl`)**: The incoming URL pathname has repeated slashes normalized, trailing slashes adjusted, and RSC or prefetch headers inspected (`RSCPathnameNormalizer`, `SegmentPrefixRSCPathnameNormalizer`).
2. **Locale Analysis (`I18NProvider`)**: If internationalization is configured, the request pathname is analyzed to detect and strip domain or path locales.
3. **Matcher Manager Invocation (`DefaultRouteMatcherManager.match`)**: The normalized pathname (with a guaranteed leading slash via `ensureLeadingSlash`) is passed to `matchers.match(pathname, options)`.

Sources: [packages/next/src/server/route-matcher-managers/default-route-matcher-manager.ts:245-263](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matcher-managers/default-route-matcher-manager.ts#L245-L263), [packages/next/src/server/route-modules/app-page/module.ts:116-153](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-modules/app-page/module.ts#L116-L153)

4. **Static Validation (`validate`)**: `matchAll` first checks `this.matchers.static`. If `!isDynamicRoute(pathname)`, static matchers are tested instantly against the pathname.
5. **Dynamic Search and Sorting**: If no static match occurs, dynamic matchers in `this.matchers.dynamic` are evaluated in sorted order of specificity.
6. **Request Meta Attachment**: Once a `RouteMatch` object containing the `definition` and `params` is returned, it is attached to request metadata via `addRequestMeta(req, 'match', match)` to prevent redundant matching during subsequent rendering phases.

Sources: [packages/next/src/server/next-server.ts:1117-1120](https://github.com/blade47/next.js/blob/main/packages/next/src/server/next-server.ts#L1117-L1120), [packages/next/src/server/route-matcher-managers/default-route-matcher-manager.ts:264-287](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matcher-managers/default-route-matcher-manager.ts#L264-L287)

---

## Client-Side Optimistic Route Matching (Segment Cache)

In the App Router client, the segment cache implements optimistic routing via `optimistic-routes.ts`. This module predicts route structures for URLs that have not been prefetched yet by traversing a trie of `KnownRoutePart` nodes.

Sources: [packages/next/src/client/components/segment-cache/optimistic-routes.ts:1-44](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L1-L44)

```mermaid
erDiagram
    KnownRoutePart {
        Map staticChildren
        FulfilledRouteCacheEntry pattern
        KnownRoutePart dynamicChild
        string dynamicChildParamName
        string dynamicChildParamType
    }
    RouteTree ||--o{ KnownRoutePart : "discovers"
    FulfilledRouteCacheEntry ||--o{ KnownRoutePart : "serves as pattern"
```

Sources: [packages/next/src/client/components/segment-cache/optimistic-routes.ts:70-136](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L70-L136)

When `matchKnownRoute(now, pathname, search)` is called, it splits the pathname into segments and invokes `matchKnownRoutePart`:
- **Static Children Priority**: Exact static segment matches take precedence over dynamic children to prevent `/blog/featured` from incorrectly matching `/blog/[slug]`.
- **Dynamic Matching**: Evaluates regular dynamic segments (`[param]`, type `'d'`), required catch-alls (`[...param]`, type `'c'`), and optional catch-alls (`...param`, type `'oc'`), accumulating parameter values in `resolvedParams`.
- **Rewrite De-opt**: If a mismatch is detected due to a dynamic rewrite (`hasDynamicRewrite`), the pattern flags the entry and bails out to server resolution.

Sources: [packages/next/src/client/components/segment-cache/optimistic-routes.ts:607-693](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L607-L693), [packages/next/src/client/components/segment-cache/optimistic-routes.ts:743-845](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L743-L845)

---

## Design Trade-Offs

| Design Choice | Benefit | Cost |
| :--- | :--- | :--- |
| **Manifest-Driven Providers** | Decouples route discovery from the request path; avoids filesystem IO during active requests. | Requires manifest synchronization during build or HMR compilation updates. |
| **Separated Static and Dynamic Matcher Arrays** | Allows $O(1)$ or fast direct checks for non-dynamic pathnames, bypassing regex evaluation. | Requires sorting dynamic routes upon registration to preserve specificity order. |
| **Client-Side Trie (Known Routes)** | Bypasses server round-trips for predicted route structures, improving transition performance. | Increases client memory usage (append-only trie) and requires de-opting on dynamic rewrites or interception routes. |

Sources: [packages/next/src/server/route-matcher-managers/default-route-matcher-manager.ts:13-17](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matcher-managers/default-route-matcher-manager.ts#L13-L17), [packages/next/src/client/components/segment-cache/optimistic-routes.ts:33-44](https://github.com/blade47/next.js/blob/main/packages/next/src/client/components/segment-cache/optimistic-routes.ts#L33-L44)

---

## Worked Example: Server-Side Route Matching Integration

The following example demonstrates how `BaseServer` initializes route matchers and how `NextServer` consumes route matches to execute Pages API routes or render pages:

```typescript
import { DefaultRouteMatcherManager } from './route-matcher-managers/default-route-matcher-manager'
import { PagesRouteMatcherProvider } from './route-matcher-providers/pages-route-matcher-provider'
import { ServerManifestLoader } from './route-matcher-providers/helpers/manifest-loaders/server-manifest-loader'
import { PAGES_MANIFEST } from '../shared/lib/constants'

// 1. Create a server manifest loader
const manifestLoader = new ServerManifestLoader((name) => {
  if (name === PAGES_MANIFEST) {
    return { '/about': 'pages/about.js' }
  }
  return null
})

// 2. Instantiate matcher manager and register provider
const matchers = new DefaultRouteMatcherManager()
matchers.push(new PagesRouteMatcherProvider('/dist', manifestLoader))

// 3. Wait for readiness and match a pathname
await matchers.waitTillReady()
const match = await matchers.match('/about', {})

if (match) {
  console.log(`Matched route definition:`, match.definition.page)
  // Output: Matched route definition: /about
}
```

Sources: [packages/next/src/server/base-server.ts:802-848](https://github.com/blade47/next.js/blob/main/packages/next/src/server/base-server.ts#L802-L848), [packages/next/src/server/route-matcher-managers/default-route-matcher-manager.ts:37-53](https://github.com/blade47/next.js/blob/main/packages/next/src/server/route-matcher-managers/default-route-matcher-manager.ts#L37-L53)

## Related

- [Routing and Normalization](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/server-runtime/routing-and-normalization)
- [App Server Rendering](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/app-router-rendering/app-server-rendering)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/llms.txt) for all pages in this wiki.
