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
# Via the meta-package
npm install remix
# Or individually
npm install @remix-run/route-patternImports
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
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
| Syntax | Name | Example | Matches |
|---|---|---|---|
/static | Static segment | /about | Exactly /about |
/:param | Dynamic segment | /users/:id | /users/42, /users/alice |
/*wildcard | Wildcard segment | /files/*path | /files/a/b/c.txt |
/(:optional) | Optional segment | /users/(:id) | /users or /users/42 |
Static segments match the literal text:
let pattern = new RoutePattern('/about')
pattern.match(new URL('http://localhost/about')) // match
pattern.match(new URL('http://localhost/other')) // nullDynamic segments (:param) match a single path segment (no slashes):
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:
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:
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:
// 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.
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
| Option | Type | Default | Description |
|---|---|---|---|
ignoreCase | boolean | false | When 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.
let pattern = new RoutePattern('/users/:id')
pattern.test(new URL('http://localhost/users/42')) // true
pattern.test(new URL('http://localhost/other')) // falsepattern.href(...args)
Generates a URL string from the pattern and the provided params.
let pattern = new RoutePattern('/users/:id')
pattern.href({ id: '42' }) // "/users/42"For patterns with no dynamic segments:
let pattern = new RoutePattern('/about')
pattern.href() // "/about"With search params (second argument):
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.
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
| Property | Type | Description |
|---|---|---|
source | string | The serialized pattern source string. |
protocol | string | The protocol portion (e.g. 'https'), or ''. |
hostname | string | The hostname portion, or ''. |
port | string | The port portion, or ''. |
pathname | string | The pathname portion without the leading slash. |
search | string | The search constraints without the leading ?. |
ast | AST | The parsed pattern AST (advanced). |
pattern.toString()
Returns the serialized pattern source string. Same as pattern.source.
RoutePatternMatch
The object returned by pattern.match().
| Property | Type | Description |
|---|---|---|
pattern | RoutePattern | The pattern that matched. |
url | URL | The URL that was matched. |
params | Params<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.
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
- Static segments beat dynamic segments:
/users/newbeats/users/:id. - Dynamic segments beat wildcards:
/files/:namebeats/files/*path. - Longer static matches beat shorter ones.
- More search constraints beat fewer.
compare(a, b)
Compares two RoutePatternMatch objects by specificity. Both matches must be for the same URL.
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 specificascending(a, b) / descending(a, b)
Comparator functions suitable for Array.prototype.sort().
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.
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().
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.
import { TrieMatcher } from 'remix/route-pattern'
let matcher = new TrieMatcher()
// Same API as ArrayMatcherMatcher Interface
Both matchers implement the Matcher<data> interface:
| Property/Method | Type | Description |
|---|---|---|
ignoreCase | boolean | Whether pathname matching is case-insensitive. |
add(pattern, data) | void | Adds a pattern and its associated data. |
match(url, compareFn?) | Match | null | Returns the best match, or null. |
matchAll(url, compareFn?) | Match[] | Returns all matches, sorted by specificity. |
ParseError
Thrown when a pattern string is syntactically invalid.
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.
import type { Join } from 'remix/route-pattern'
type Joined = Join<'/api', '/users/:id'>
// "/api/users/:id"Complete Example
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"Related Packages
- fetch-router --- The router that uses
route-patterninternally for URL matching.
Related Guides
- Routing In Depth --- How route patterns work in the context of a full application.