Client-side routing
Turn it on and links stop reloading the page: the next document is fetched, its markup swapped in, and its bindings started. Pages are prefetched when the pointer reaches a link, so a click usually has nothing left to wait for.
Turning it on
Add "router": true to your project's package.json. It is off by default, because changing how every link behaves is not something a project should get without asking.
{
"name": "my-app",
"type": "module",
"router": true
}Nothing else changes. Keep writing ordinary <a href="/about"> links — the router attaches itself to the ones it can handle.
What it does
| Event | Behaviour |
|---|---|
| Pointer reaches a link | That page is fetched and held, so the click is instant |
| Click | Markup swapped, title and meta tags updated, scrolled to the top |
| Back / forward | Swapped back, with the scroll position the browser remembered |
| Anything unexpected | Falls back to a full page load, so navigation always completes |
Links it leaves alone
A link is only intercepted when a plain client-side navigation is clearly what was meant.
<a href="https://example.com">another origin</a> <a href="/report.pdf" download>a download</a> <a href="/about" target="_blank">a new tab</a> <a href="#section">a spot on this page</a> <!-- and opt out explicitly wherever you need to --> <a href="/legacy" data-no-router>skip the router</a>
Cmd-click, Ctrl-click and middle-click are left alone too, so opening a link in a new tab keeps working.
What happens to your state
Each page starts fresh. Navigating runs the new page's module from scratch, so its signals begin at their initial values. Nothing is carried across a navigation.
This is the honest consequence of pages being independent documents. If you need a value to survive navigation, put it somewhere that outlives the page — sessionStorage, or the URL itself.
Without JavaScript
Every page is server-rendered and every link is a real <a>. With the script blocked or still loading, the site navigates the ordinary way and nothing is missing. The router is an enhancement on top of something that already works.
Cost
Around 2 KB, loaded once and shared by every page. It is loaded after the page's own module, so a page is interactive before navigation is enhanced.
Knowing when a page changed
A page's own module is re-run on every navigation, so its bindings work without you doing anything. A plain <script> that enhances the markup is different: it ran once when the site first loaded, and the nodes it worked on have just been replaced.
The router announces each swap so such a script can run again.
function enhance() {
for (const heading of document.querySelectorAll('h2')) {
heading.id = heading.textContent.toLowerCase().replace(/\s+/g, '-');
}
}
enhance();
document.addEventListener('azox:navigate', enhance);The event fires after the new markup is in place and after the page's module has run, so a listener always sees the finished page. event.detail holds url and from, which is enough to tell a section change from a move to another part of the site.
Next
- Routing — how files become URLs
- Deployment