Mithril v2 client-side routing with m.route
Mount Mithril's router once, use m.route.link for anchors, and read vnode.attrs for live route parameters.
11 Nov 2025, 02:32 UTC

The problem: full reloads and stale route parameters
If clicking a link in your Mithril application reloads the entire page, or the user ID shown on /user/42 stays stuck after navigating to /user/7, the router is probably being used incorrectly. Mithril v2 handles navigation entirely in the browser, but only when anchors and parameters are wired through its routing API.
Useful takeaway: mount the router once with m.route, generate anchor href values with m.route.link, read route parameters from vnode.attrs on every render, and call m.redraw only when state changes outside Mithril's normal event and request flow.
One routing configuration
This guide assumes Mithril v2. Save the following as a local HTML file and open it directly in a browser; no build step or server is required.
<!DOCTYPE html><html lang='en'><head><meta charset='UTF-8'><title>Mithril routing example</title></head><body><script src='https://cdn.jsdelivr.net/npm/mithril@2/mithril.min.js'></script><script>const Home = { view() { return m('div', [ m('h1', 'Home'), m('a', { href: m.route.link('/user/42') }, 'Open user 42') ] ) } } const User = { oninit(vnode) { console.log('Route parameter id:', vnode.attrs.id) }, view(vnode) { return m('div', [ m('h1', 'User profile'), m('p', 'ID: ' + vnode.attrs.id), m('a', { href: m.route.link('/') }, 'Back to home') ] ) } } m.route(document.body, '/', { '/': Home, '/user/:id': User }) </script></body></html>m.route(document.body, '/', routes) mounts the router on document.body, sets / as the default route, and maps path patterns to component objects. The pattern /user/:id captures a URL segment and exposes it as vnode.attrs.id.
Clicking the anchor uses m.route.link('/user/42') for its href. Mithril intercepts the click, updates the browser URL, and renders the User component without a full page reload.
Programmatic navigation
Run these checks in the browser console on the example page:
m.route.set('/user/7')// Expected: address bar becomes /user/7 and the User view renders ID 7m.route.get()// Expected: '/user/7'Navigation does not reload the page. Use m.route.set for redirects, form submissions, or application logic; use m.route.get to inspect the current path.
Reading route parameters safely
Mithril reuses route components when the URL changes between matching patterns. That means the User component instance may persist when moving from /user/42 to /user/7. Always read the identifier from vnode.attrs.id during render or lifecycle hooks instead of copying it into component state once.
| Approach | Result when the ID changes |
|---|---|
| Read vnode.attrs.id in view | Shows the current URL parameter |
| Save vnode.attrs.id to component state in oninit only | Can show a stale ID after the parameter changes |
Common mistakes and limits
- Raw anchor href. Writing href='/user/7' causes a normal browser request and full reload. Use href={m.route.link('/user/7')} instead.
- Multiple router mounts. Avoid calling m.route.mount on an element already controlled by m.route. Prefer a single m.route call per application root.
- External asynchronous code. setTimeout, raw fetch callbacks, and third-party event handlers run outside Mithril's automatic redraw cycle. Wrap state updates in m.redraw() or use m.request when the UI must reflect the change.
- Direct URL refreshes. With path-style routes, refreshing /user/7 asks the server for that path. The server must return the application shell, or the user gets a 404. A hash prefix such as # avoids this, but changes the URL shape.
How to verify the result
- Open the local HTML file and click the Open user 42 link.
- Confirm the URL changes to /user/42 and the page content updates without a reload.
- Open DevTools, run m.route.set('/user/9'), and confirm the rendered ID becomes 9.
- Check the console log from oninit to confirm vnode.attrs.id matches the active path.
Because this is a client-side configuration change with no persistent server state, refreshing the page returns the application to the default route; no separate rollback procedure is needed.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.