alpinejs-router: A Lightweight Client-side Router for Alpine.js

· 3 min read

@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/:id maps 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 statesx-link.activity adds active / exact-active classes (configurable) so navigation reflects the current route.
  • Two history modeshash mode 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

If you use Alpine.js and need client-side routing, give it a try. Bug reports, feature requests and pull requests are all welcome.