URLPattern (URL and Network Information) compiles router syntax into a matcher that covers every URL component — protocol, hostname, port, pathname, search, hash — so one object checks host and path together. :name is a named group, :name(\d+) constrains it with a regular expression, * is a wildcard and {...}? an optional group; test() returns a boolean, exec() a groups map per component or null.
<pre id="out" style="font: 14px monospace; margin: 4px"></pre>
<script>
const routes = [['/books/:id(\\d+)', 'book'], ['/books/:id/reviews{/:page}?', 'reviews'],
['/assets/*', 'asset']].map(([p, name]) => [new URLPattern({ pathname: p }), name]);
const route = (url) => {
const hit = routes.find(([p]) => p.test(url));
return hit ? `${hit[1]} ${JSON.stringify(hit[0].exec(url).pathname.groups)}` : 'no route';
};
out.textContent = ['/books/42', '/books/42/reviews/3', '/books/x', '/assets/img/a.png']
.map((p) => `${p.padEnd(19)} -> ${route('https://shop.dev' + p)}`).join('\n');
</script>
The hand-written first route, /^\/books\/(\d+)\/?$/, escapes every slash, numbers its captures and ignores the host. Feed event.destination.url from the Navigation API (History and Navigation) to the same table and you have a router, in workers too. Baseline Newly available since September 2025 (Chrome 95 1 , Firefox 142 555 , Safari 26 10 ); older browsers need urlpattern-polyfill 296 (https://github.com/kenchris/urlpattern-polyfill 296 ).