npm package: @jreusch/router-vue
Description: Vue bindings for @jreusch/router
Author: Joshua Reusch
Latest version: 1.0.2

Vue 3 library for [@jreusch/router](https://www.npmjs.com/package/@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:

```bash
npm install @jreusch/router-vue
```

Choose `<HashRouter>` or `<PathRouter>`, and wrap your pages inside of the `<Route>` component:

```vue
<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:

```vue
<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](https://gitlab.com/arkandos/ts-router/-/blob/master/packages/core/README.md#in-depth-syntax-and-semantics) in the core API documentation.

# Components

## &lt;PathRouter&gt;, &lt;HashRouter&gt;, &lt;MemoryRouter&gt;, &lt;PathWithBaseRouter&gt;

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](https://gitlab.com/arkandos/ts-router/-/blob/master/packages/core/README.md#client-side-routers), so you can also go there to learn more about their differences!

The different flavors of routers change which internal [router object](https://gitlab.com/arkandos/ts-router/-/blob/master/packages/core/README.md#client-side-routers) 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](#dynamic-routers).


### Props

| Name       | Type                                       | Description
|------------|--------------------------------------------|-------------
| `base`     | `string`                                   | Base URL to strip from the path. (`<PathWithBaseRouter>` only)

See also [Dynamic Routers](#dynamic-routers) if you need to switch between different Router components depending on the context.

## &lt;Route&gt;

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](https://vuejs.org/guide/components/slots.html#scoped-slots). If you don't want to pass down the params yourself, [the useParams hook](#useparams-paramsnull) 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](https://vuejs.org/guide/components/async.html), using built-in vue features.

```vue
<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


## &lt;Redirect&gt;

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.

```vue
<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.

## &lt;Link&gt;

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.

```vue
<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](#usematchpattern-string-allowpartial-boolean-paramsnull) 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&lt;string&gt;, allowPartial?: MaybeComputedUnref&lt;boolean&gt;): 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](https://vueuse.org/shared/resolveUnref/) 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!

```js
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:

```js
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](https://gitlab.com/arkandos/ts-router/-/blob/master/packages/core/README.md#router-api) 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](https://gitlab.com/arkandos/ts-router/-/blob/master/packages/core/README.md#in-depth-syntax-and-semantics) on how patterns, segments and matching works. I highly recommend to check it out!

The entire API of [@jreusch/router](https://www.npmjs.com/package/@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](https://gitlab.com/arkandos/ts-router/-/blob/master/packages/core/README.md#router-api) as a prop, making it possible to dynamically switch between them:

```vue
<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](https://joshi.monster)
If you really like what you see, you can [Buy me more ☕](https://www.paypal.com/donate/?hosted_button_id=CNT7EUVAM6MZS), or [get in touch](jreusch4+oss@gmail.com)!

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!