alpinejs-router: A Lightweight Client-side Router for Alpine.js
@shaun/alpinejs-router is a lightweight client-side router for Alpine.js with dynamic params, external templates, and hash or HTML5 history modes.
It adds declarative routes, link navigation, and programmatic navigation as an Alpine.js plugin — no framework, no build step.
The idea is simple: if you build an Alpine.js site and need client-side routing, you should not have to pull in a full SPA framework or a heavyweight router to get it.
Install
npm install @shaun/alpinejs-router
import Alpine from 'alpinejs'
import router from '@shaun/alpinejs-router'
Alpine.plugin(router)
Alpine.start()
It works with yarn and bun too, and there is a CDN build for zero-build setups:
<script src="https://unpkg.com/@shaun/[email protected]/dist/cdn.min.js" defer></script>
<script src="https://unpkg.com/[email protected]/dist/cdn.min.js" defer></script>
Quick start
Routes are declared directly in your HTML with x-route templates, and links are upgraded with the x-link directive:
<body x-data>
<nav>
<a x-link href="/">Home</a>
<a x-link href="/hello/alpine">Hello</a>
</nav>
<template x-route="/">
<main>Home</main>
</template>
<template x-route="/hello/:name">
<main>Hello <span x-text="$router.params.name"></span></main>
</template>
<template x-route.notfound>
<main>Not found</main>
</template>
</body>
No build step, no framework — just Alpine.
Highlights
- Dynamic params —
/users/:idmaps to$router.params.id, with optional custom regex constraints like/:orderId(\d+)when you need to tell two routes apart on the same path. - External templates — load route templates from separate HTML files via
template="/somewhere.html", including preload support and inline fallbacks when a template cannot be fetched. - Programmatic navigation —
$router.push('/path')and$router.replace('/path'), mirroring the browser history API. - Active link states —
x-link.activityaddsactive/exact-activeclasses (configurable) so navigation reflects the current route. - Two history modes —
hashmode works on any static host;web(HTML5) mode gives clean URLs when your server has a catch-all fallback. - Lightweight — a single Alpine.js plugin with no runtime dependencies and no build-tool requirements.
Performance
Routing should not be the bottleneck. Example result from npm run bench:router on Node.js v26 (4000 routes):
| Case | ops/sec | us/op |
|---|---|---|
| match static | 6,253,583 | 0.160 |
| match dynamic first | 1,271,974 | 0.786 |
| match dynamic last | 1,287,858 | 0.776 |
| match miss | 4,129,862 | 0.242 |
| cached is() | 3,610,343 | 0.277 |
| notfound miss | 4,148,457 | 0.241 |
Static route matching and cached is() checks stay effectively constant-time. Dynamic matching is indexed by the first path segment, with wildcard-first dynamic routes used as a fallback.
Recognition
pinecone-router, one of the most popular routers in the Alpine.js ecosystem, credits this project in its README:
Code from @shaun/alpinejs-router is licensed under the MIT License. Copyright (c) 2022 Shaun Li
@shaun/alpinejs-router for being a reference of how things can be done differently.
It is genuinely rewarding to see other router authors reference this work in theirs.
Related projects
I also maintain a lightweight router for Svelte for apps that skip SvelteKit. See all my open-source packages on the Projects page.
Project links
- npm: @shaun/alpinejs-router
- GitHub: shaunlee/alpinejs-router
- License: MIT
If you use Alpine.js and need client-side routing, give it a try. Bug reports, feature requests and pull requests are all welcome.