@jreusch/router-vue v1.0.2
Vue 3 library for @jreusch/router, with an react-router
-inspired API using components, instead of the configuration-based approach of vue-router
.
Features
- 🚀 Tiny - only 200LOC of Typescript, ~2k minified+gziped in production
- 🤘 Well-tested - Lots of tests for the core functionality
- 🤩 Powerful supports custom patterns, backtracking, different router backends, automatic props, async routes, and more!
Quick Start
Install the library, you do not need to instead @jreusch/router
separately:
npm install @jreusch/router-vue
Choose <HashRouter>
or <PathRouter>
, and wrap your pages inside of the <Route>
component:
<template>
<PathRouter>
<Route pattern="/">
<Home />
</Route>
<!-- use scoped slots to access params -->
<Route pattern="/blog/:postId" v-slot="{ postId }">
<BlogPost :post-id="postId" />
</Route>
<!-- catch-all route at the end -->
<Route pattern="/*">
<p>Not found!</p>
</Route>
</PathRouter>
</template>
<script setup>
import { PathRouter, Route } from '@jreusch/router-vue'
import Home from './pages/Home.vue'
import BlogPost from './pages/BlogPost.vue'
</script>
Use <Link>
instead of <a>
tags to enable client-side navigation and active/inactive states:
<template>
<nav>
<ul>
<li v-for="link in links" :key="link.to">
<Link :href="link.to" class="navbar-link" active-class="active">
{{ link.label }}
</Link>
</li>
</ul>
</nav>
</template>
<script>
import { Link } from '@jreusch/router-vue'
const links = [
{ to: '/', label: 'Home' },
{ to: '/blog', label: 'Blog' },
{ to: '/about', label: 'About us' }
]
</script>
Read the rest of the documentation to see what else is there 🙂
You might also want to learn more about the pattern syntax in the core API documentation.
Components
<PathRouter>, <HashRouter>, <MemoryRouter>, <PathWithBaseRouter>
Wrap the root of your application inside of a router component. The router provides the context, which all other hooks and components depend upon, and defines global configuration, like where to get the URL from, and what happens if you navigate somewhere else. A router makes sure only a single route is rendered at a time.
Routers
Name | Description |
---|---|
PathRouter | The default choice for modern SPAs. Uses the path and provides client-side navigation, but requires some server configuration to work properly. |
PathWithBaseRouter | Similar to PathRouter , but also allows you to specify a base URL. |
HashRouter | If changing the server configuration is not possible, you can use the HashRouter to instead use hash URLs. |
MemoryRouter | A router that works fully in-memory and can be used for server-side rendering. |
Router components correspond to the createXYZRouter()
functions in the core library, so you can also go there to learn more about their differences!
The different flavors of routers change which internal router object is used, but behave exactly the same otherwise. They are in thin convenience wrappers around the <Router>
component, which allows you to control the router dynamically.
Props
Name | Type | Description |
---|---|---|
base | string | Base URL to strip from the path. (<PathWithBaseRouter> only) |
See also Dynamic Routers if you need to switch between different Router components depending on the context.
<Route>
The <Route>
component wraps your page, only rendering its children if the URL matches. It needs to be placed inside of a *Router
component to access the context. This will make sure there will only be a single active <Route>
per router. Routes are checked in-order, so if you have multiple routes matching the same URL, only the first one will be active and rendered.
Inside of the <Route>
, you can access the parsed params using scoped slots. If you don't want to pass down the params yourself, the useParams hook provides a way to access the params inside of the children of the <Route>
.
To asynchronously load a page and enable bundle-splitting, import the page component as an async component, using built-in vue features.
<template>
<!-- directly specify contents -->
<Route pattern="/">
<h1>Welcome to my site</h1>
<p>
I'm so happy to see you!
</p>
</Route>
<!-- use scoped slots / v-slot to access params -->
<Route pattern="/blog/:postId" v-slot="{ postId }">
<h1>Post {{ postId }}</h1>
<p>Lorem ipsum dolor sit amet..</p>
</Route>
<!-- async components enable bundle splitting -->
<Route pattern="/async">
<Async />
</Route>
<!-- routes are matched in-order, so specifying a catch-all last will act as a 404 -->
<Route pattern="/*">
<h1>Not found!</h1>
</Route>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
import { Route } from '@jreusch/router-vue'
const Async = defineAsyncComponent(() => import('./pages/Async.vue'))
</script>
Props
Name | Type | Description |
---|---|---|
pattern | string | Render this route whenever this pattern matches |
<Redirect>
A render-less component that redirects to a new location as soon as a pattern matches. Variables used in the from
pattern can be re-used in the to
pattern to make dynamic redirects.
By default, <Redirect>
will replace the current URL, but you can also explicitely set :replace="false"
to disable this behaviour.
A redirect will check if there is another <Route>
that matches the URL, and will only redirect if it would be the active route, i.e. the one being rendered.
<template>
<!-- static redirect, URL has to be exactly /rambling to redirect to /blog -->
<Redirect from="/ramblings" to="/blog" />
<!-- redirect /about to /about-us, pushing a new url -->
<Redirect from="/about" to="/about-us" :replace="false" />
<!-- use variables to redirect a prefix:
e.g. /posts/hello-sailor will be redirected to /blog/hello-sailor
-->
<Redirect from="/posts/:slug*" to="/blog/:slug*" />
</template>
<script setup>
import { Redirect } from '@jreusch/router-vue'
</script>
Props
Name | Type | Description |
---|---|---|
from | string | Redirect whenever current URL matches this pattern |
to | string | Redirect to this other pattern, stringified with the params returned by the match |
replace | boolean? | If false , navigate to the new URL; replace the current URL otherwise. |
<Link>
Use <Link>
whenever you want an internal link with client-side navigation enabled. Links enable different classes based on whether or not the link's URL matches the current URL, making them perfect for nav bars. By default, a link is considered "active" whenever the current URL starts with the url in the link.
<template>
<Link href="/" class="logo">
<Logo />
</Link>
<!-- set .active class whenver the URL starts with /blog -->
<Link href="/blog" class="nav-link" active-class="active">
Blog
</Link>
<!-- set .active class only when the URL is exactly /about-us,
but not on /about-us/contact
-->
<Link href="/about-us" class="nav-link" active-class="active" exact>
About us
</Link>
<!-- you can use patterns and params to build urls, too: -->
<Link href="/blog/:postId" :params="{ postId: 1234 }">
What I've been up to
</Link>
</template>
<script setup>
import { Link } from '@jreusch/router-vue'
import Logo from '../components/Logo.vue'
</script>
If you need something more custom, check out the useMatch hook instead!
Props
Name | Type | Description |
---|---|---|
href | string | Internal URL or pattern to navigate to when clicked |
params | Params? | If set, use these params to stringify the given pattern |
activeClass | unknown? | Class that is set if the current URL matches the given pattern |
inactiveClass | unknown? | Class that is set if the current URL does not match the given pattern |
exact | boolean? | If true , the link is considered active if the given pattern matches exactly. Otherwise, a the given pattern has to only match the beginning of the current URL. |
Composition API
useCurrentUrl(): Ref<string|null>
Get the current URL that the router uses as a reactive (readonly) value
Because the current router might not match the URL at all (for example when using a <PathWithBaseRouter>
), this function might return null
to indicate that.
useMatch(pattern: MaybeComputedUnref<string>, allowPartial?: MaybeComputedUnref<boolean>): Ref<Params|null>
Provided a pattern (as a string), returns a computed that matches the current URL against that pattern, returning the variables if it matches, or null
if it doesn't.
MaybeComputedUnref
is a special type I've stolen adapted from vueuse to allow you to either pass a value directly, a function returning a value (maybe extracted from props
), or a computed ref. This makes it easy to pass values from props without needing to toRefs
them first!
const indexMatch = useMatch('/about') // static pattern
// reactive match, updates whenever props.postId changes
const blogMatch = useMatch(() => `/blog/${props.postId}`)
This hook can for example be used to build custom <Link>
components.
Setting the allowPartial
flag, the pattern does not need to match the entire URL, but will early-out as soon as a portion of the URL matches.
useRouter(): Router
Get access to the underlying @jreusch/router
router object, allowing programmatic navigation:
const router = useRouter()
router.navigate('/somewhere-else') // go to a new url
router.go(-1) // go back
router.go(1) // go forward
You can look at the router documentation to learn about all the methods available!
useParams(): Ref<Params|null>
A hook that can be used inside of a <Route>
, where it will provide the parsed params, without needing to pass it down. It does not allow to access the parameters outside of the <Route>
.
Advanced
I wrote a guide on how patterns, segments and matching works. I highly recommend to check it out!
The entire API of @jreusch/router is also exported in this package, so if you want to parse
and match
manually, you can just import them directly.
Dynamic Routers
<PathRouter>
, <HashRouter>
, etc. provide simple and convenient components for when you just want to build a simple client-side app. But what if you need to support SSR from the same codebase as well? What if you're building a widget, and other people should be able to control how routing works?
The more low-level <Router>
component allows you to provide a @jreusch/router router object as a prop, making it possible to dynamically switch between them:
<template>
<Router :router="router">
...
</Router>
</template>
<script setup>
import { createPathRouter, createMemoryRouter, Router } from '@jreusch/router-vue'
import { computed } from 'vue'
const router = computed(() => {
if (isSSR()) {
return createMemoryRouter()
} else {
return createPathRouter()
}
})
</script>
The underlying router can be swapped at runtime, updating all subscriptions automatically.
Route order and the rendering process
The rendering process of this library is unbelievably simple: On every URL change, all <Route>
components re-render, and the first matching one sets some global state to let the others know that a match has been found. There is no path-rank, no special data structure, or crazy component interactions to register/unregister routes.
While some of those techniques might make routing more predictable and/or faster, I believe that most SPAs don't have hundreds or thausands of individual routes, where complex data structures would outperform a simple array.
One caveat of this is that it depends on the order the <Route>
components where first rendered, so it's almost always easiest to just keep them as direct children to the <Router>
, making the order obvious. In general, it is best to not have overlapping routes at all, except for a single "catch-all" route at the end.
Support / Climate action
This library was made with ☕, 💦 and 💜 by joshua If you really like what you see, you can Buy me more ☕, or get in touch!
If you work on limiting climate change, preserving the environment, et al. and any of my software is useful to you, please let me know if I can help and prioritize issues!