Skip to content

route-pattern

The route-pattern package provides the URL pattern matching engine that powers Remix's router. It parses URL patterns with dynamic segments and wildcards, matches URLs against those patterns, generates URLs from patterns and params, and compares matches by specificity.

You rarely need to use this package directly --- the fetch-router uses it internally. But it is useful when you need low-level control over pattern matching, or when building tools that work with URL patterns.

Installation

bash
# Via the meta-package
npm install remix

# Or individually
npm install @remix-run/route-pattern

Imports

ts
import {
  RoutePattern,
  ParseError,
  ArrayMatcher,
  TrieMatcher,
} from 'remix/route-pattern'

// Specificity comparison
import {
  compare,
  ascending,
  descending,
  lessThan,
  greaterThan,
  equal,
} from 'remix/route-pattern/specificity'

// Types
import type {
  RoutePatternMatch,
  Params,
  HrefArgs,
  Matcher,
  Match,
  Join,
} from 'remix/route-pattern'

RoutePattern

The core class for parsing, matching, and generating URLs.

Constructor

ts
let pattern = new RoutePattern('/users/:id')

The constructor parses the pattern string into an internal AST. Throws ParseError if the pattern is invalid.

Pattern Syntax

SyntaxNameExampleMatches
/staticStatic segment/aboutExactly /about
/:paramDynamic segment/users/:id/users/42, /users/alice
/*wildcardWildcard segment/files/*path/files/a/b/c.txt
/(:optional)Optional segment/users/(:id)/users or /users/42

Static segments match the literal text:

ts
let pattern = new RoutePattern('/about')
pattern.match(new URL('http://localhost/about'))  // match
pattern.match(new URL('http://localhost/other'))  // null

Dynamic segments (:param) match a single path segment (no slashes):

ts
let pattern = new RoutePattern('/users/:id')
let match = pattern.match(new URL('http://localhost/users/42'))
match.params // { id: "42" }

Wildcard segments (*name) match everything after that point, including slashes:

ts
let pattern = new RoutePattern('/files/*path')
let match = pattern.match(new URL('http://localhost/files/docs/readme.md'))
match.params // { path: "docs/readme.md" }

Optional segments are wrapped in parentheses:

ts
let pattern = new RoutePattern('/users/(:id)')
pattern.match(new URL('http://localhost/users'))     // match, params.id is undefined
pattern.match(new URL('http://localhost/users/42'))   // match, params.id is "42"

Advanced Patterns

Patterns can also match protocols, hostnames, ports, and query parameters:

ts
// Protocol matching
new RoutePattern('https:///secure')

// Hostname matching
new RoutePattern('http://api.example.com/v1/users')

// Hostname with dynamic subdomain
new RoutePattern('//:tenant.example.com/dashboard')

// Port matching
new RoutePattern('//localhost:3000/api')

// Query parameter constraints
new RoutePattern('/search?q')         // "q" param must be present
new RoutePattern('/filter?type=book') // "type" param must equal "book"

pattern.match(url, options?)

Attempts to match a URL against the pattern. Returns a RoutePatternMatch object on success, or null if the URL does not match.

ts
let pattern = new RoutePattern('/users/:userId/posts/:postId')
let match = pattern.match(new URL('http://localhost/users/5/posts/99'))

if (match) {
  match.params    // { userId: "5", postId: "99" }
  match.url       // URL object
  match.pattern   // the RoutePattern instance
  match.paramsMeta // detailed match info for hostname and pathname
}

Options

OptionTypeDefaultDescription
ignoreCasebooleanfalseWhen true, pathname matching is case-insensitive. Hostname matching is always case-insensitive.

pattern.test(url)

Returns true if the URL matches the pattern, false otherwise. A lightweight alternative to match() when you do not need the match details.

ts
let pattern = new RoutePattern('/users/:id')
pattern.test(new URL('http://localhost/users/42'))  // true
pattern.test(new URL('http://localhost/other'))      // false

pattern.href(...args)

Generates a URL string from the pattern and the provided params.

ts
let pattern = new RoutePattern('/users/:id')
pattern.href({ id: '42' })  // "/users/42"

For patterns with no dynamic segments:

ts
let pattern = new RoutePattern('/about')
pattern.href()  // "/about"

With search params (second argument):

ts
let pattern = new RoutePattern('/search')
pattern.href({}, { q: 'remix' })  // "/search?q=remix"

Throws HrefError if required params are missing.

pattern.join(other)

Joins this pattern with another pattern or pathname string. Returns a new RoutePattern.

ts
let base = new RoutePattern('/api/v1')
let endpoint = new RoutePattern('/users/:id')
let joined = base.join(endpoint)

joined.href({ id: '42' })  // "/api/v1/users/42"

The joined pattern inherits protocol, hostname, and port from the other pattern if specified, otherwise from this.

Properties

PropertyTypeDescription
sourcestringThe serialized pattern source string.
protocolstringThe protocol portion (e.g. 'https'), or ''.
hostnamestringThe hostname portion, or ''.
portstringThe port portion, or ''.
pathnamestringThe pathname portion without the leading slash.
searchstringThe search constraints without the leading ?.
astASTThe parsed pattern AST (advanced).

pattern.toString()

Returns the serialized pattern source string. Same as pattern.source.


RoutePatternMatch

The object returned by pattern.match().

PropertyTypeDescription
patternRoutePatternThe pattern that matched.
urlURLThe URL that was matched.
paramsParams<source>Extracted route parameters.
paramsMeta{ hostname, pathname }Detailed match metadata with indices for each matched segment.

Params<source>

A type-level utility that extracts the params type from a pattern string.

ts
import type { Params } from 'remix/route-pattern'

type UserParams = Params<'/users/:id'>
// { id: string }

type PostParams = Params<'/users/:userId/posts/:postId'>
// { userId: string; postId: string }

type FileParams = Params<'/files/*path'>
// { path: string }

type OptionalParams = Params<'/users/(:id)'>
// { id: string | undefined }

This is the same inference that flows through the router's BuildAction, Controller, and href() types.


Specificity

When multiple patterns match the same URL, specificity determines which match takes priority. The remix/route-pattern/specificity module provides comparison functions.

Rules

  1. Static segments beat dynamic segments: /users/new beats /users/:id.
  2. Dynamic segments beat wildcards: /files/:name beats /files/*path.
  3. Longer static matches beat shorter ones.
  4. More search constraints beat fewer.

compare(a, b)

Compares two RoutePatternMatch objects by specificity. Both matches must be for the same URL.

ts
import { compare } from 'remix/route-pattern/specificity'

let result = compare(matchA, matchB)
// -1 if a is less specific
//  0 if equal
//  1 if a is more specific

ascending(a, b) / descending(a, b)

Comparator functions suitable for Array.prototype.sort().

ts
import { ascending, descending } from 'remix/route-pattern/specificity'

// Sort from least to most specific
matches.sort(ascending)

// Sort from most to least specific
matches.sort(descending)

lessThan(a, b) / greaterThan(a, b) / equal(a, b)

Boolean comparison helpers.

ts
import { lessThan, greaterThan, equal } from 'remix/route-pattern/specificity'

if (greaterThan(matchA, matchB)) {
  // matchA is more specific
}

Matchers

Matchers are data structures that store multiple patterns and efficiently find which ones match a given URL. The route-pattern package provides two implementations.

ArrayMatcher

A simple linear matcher that tests every pattern in order. Good for small numbers of routes. This is the default matcher used by createRouter().

ts
import { ArrayMatcher, RoutePattern } from 'remix/route-pattern'

let matcher = new ArrayMatcher()
matcher.add(new RoutePattern('/users/:id'), { handler: showUser })
matcher.add(new RoutePattern('/users'), { handler: listUsers })

let match = matcher.match(new URL('http://localhost/users/42'))
match.data // { handler: showUser }
match.params // { id: "42" }

TrieMatcher

A trie-based matcher optimized for large numbers of routes. Provides faster lookups than ArrayMatcher when you have many routes.

ts
import { TrieMatcher } from 'remix/route-pattern'

let matcher = new TrieMatcher()
// Same API as ArrayMatcher

Matcher Interface

Both matchers implement the Matcher<data> interface:

Property/MethodTypeDescription
ignoreCasebooleanWhether pathname matching is case-insensitive.
add(pattern, data)voidAdds a pattern and its associated data.
match(url, compareFn?)Match | nullReturns the best match, or null.
matchAll(url, compareFn?)Match[]Returns all matches, sorted by specificity.

ParseError

Thrown when a pattern string is syntactically invalid.

ts
import { RoutePattern, ParseError } from 'remix/route-pattern'

try {
  new RoutePattern('/users/:/bad')
} catch (error) {
  if (error instanceof ParseError) {
    console.error(error.message)
  }
}

HrefError

Thrown by pattern.href() when required params are missing or the pattern cannot generate a valid URL (e.g. a hostname pattern without a hostname).


Type Utilities

Join<A, B>

A type-level utility that computes the pattern source string resulting from joining pattern A with pattern B.

ts
import type { Join } from 'remix/route-pattern'

type Joined = Join<'/api', '/users/:id'>
// "/api/users/:id"

Complete Example

ts
import { RoutePattern } from 'remix/route-pattern'
import { ascending } from 'remix/route-pattern/specificity'

// Parse patterns
let patterns = [
  new RoutePattern('/users/*rest'),
  new RoutePattern('/users/:id'),
  new RoutePattern('/users/new'),
]

// Match a URL against all patterns
let url = new URL('http://localhost/users/new')
let matches = patterns
  .map((p) => p.match(url))
  .filter((m) => m !== null)

// Sort by specificity (most specific last)
matches.sort(ascending)

// The most specific match is last
let best = matches.at(-1)!
console.log(best.pattern.source) // "/users/new"
console.log(best.params)         // {}

// Generate URLs
let userPattern = new RoutePattern('/users/:id')
console.log(userPattern.href({ id: '42' })) // "/users/42"

  • fetch-router --- The router that uses route-pattern internally for URL matching.
  • Routing In Depth --- How route patterns work in the context of a full application.

Released under the MIT License.