route-pattern
Type-safe URL matching and href generation for JavaScript. route-pattern supports path params, wildcards, optionals, and full-URL patterns with predictable ranking.
Features
- Type-Safe Params - Infer params from patterns for compile-time route correctness
- Flexible Pattern Syntax - Variables, wildcards, optionals, and query constraints
- Full URL Support - Match protocol, host, pathname, and search params
- Deterministic Ranking - Static segments beat params, and params beat wildcards
- Runtime Agnostic - Works across Node.js, Bun, Deno, Cloudflare Workers, and browsers
Installation
npm i remix
Quick Example
import { RoutePattern } from 'remix/route-pattern'
let blog = new RoutePattern('blog/:slug')
blog.match('https://remix.run/blog/v3')
blog.href({ slug: 'v3' })
let api = new RoutePattern('api(/v:version)/*path')
api.match('https://api.com/api/v2/users/profile')
api.href({ version: '2', path: 'users/profile' })
api.href({ path: 'users/profile' })
let cdn = new RoutePattern('http(s)://:region.cdn.com/assets/*file.:ext')
cdn.match('https://us-west.cdn.com/assets/images/logo.png')
cdn.href({ region: 'us-west', file: 'images/logo', ext: 'png' })
Intuitive syntax
Variables capture dynamic segments using :name:
new RoutePattern('users/:id')
new RoutePattern('blog/:year-:month-:day/:slug')
Wildcards match multi-segment paths using *name:
new RoutePattern('files/*path')
new RoutePattern('node_modules/*package/dist/index.js')
new RoutePattern('files/*')
Optionals make parts optional using ():
new RoutePattern('api(/v:version)/users')
new RoutePattern('blog/:slug(.html)')
new RoutePattern('docs(/guides/:category)')
new RoutePattern('api(/v:major(.:minor))')
Search params narrow matches using ?key, ?key=, or ?key=value. Parsing and serialization follow URLSearchParams (application/x-www-form-urlencoded): ?key and ?key= are the same constraint (stored as an empty Set in ast.search: key must be present; empty value is OK), and spaces use + / %20 like in real query strings.
new RoutePattern('search?q')
new RoutePattern('search?q=routing')
Flexible matching for partial URL patterns:
new RoutePattern('blog/:slug')
new RoutePattern('://example.com/api')
new RoutePattern('search?q')
Matchers
Match URLs against multiple patterns. Each pattern can have associated data (handlers, route IDs, metadata, etc.):
import { ArrayMatcher as Matcher } from 'remix/route-pattern'
let matcher = new Matcher<string>()
matcher.add('/', 'home')
matcher.add('blog/:slug', 'blog-post')
matcher.add('api(/v:version)/*path', 'api')
matcher.match('https://example.com/blog/v3')
matcher.match('https://example.com/api/v2/users/profile')
ArrayMatcher vs TrieMatcher
- ArrayMatcher: Best for small apps (~80 routes or fewer)
- TrieMatcher: Best for large apps (hundreds of routes)
Note: Performance depends on your specific patterns—benchmark both to verify which is faster for your app.
Both implement the Matcher API so you can swap them out easily:
import { TrieMatcher as Matcher } from 'remix/route-pattern'
Specificity
When multiple patterns match a URL, the most specific pattern wins.
Pathname specificity (left-to-right):
import { ArrayMatcher } from 'remix/route-pattern'
let matcher = new ArrayMatcher<string>()
matcher.add('blog/hello', 'static')
matcher.add('blog/:slug', 'variable')
matcher.add('blog/*path', 'wildcard')
matcher.add('*path', 'catch-all')
matcher.match('https://example.com/blog/hello')
Search parameter specificity:
let router = new ArrayMatcher<string>()
router.add('search', 'no-params')
router.add('search?q', 'has-q')
router.add('search?q=hello', 'exact-match')
router.match('https://example.com/search?q=hello')
Benchmark
To run benchmarks comparing route-pattern performance with comparable libraries:
pnpm bench bench/comparison.bench.ts
Related Work
License
See LICENSE