Routing by example
roots/router maps addresses to components. The frame below is a whole app with its own router; it keeps its route in memory (history: "memory"), so moving around in it does not change the address of this page.
// A whole app inside the example frame: its own router, kept in memory, so moving between
// its pages does not change the address of the documentation page around it.
import { createApp } from "roots/runtime"
import { createRouter } from "roots/router"
import Shell from "./Shell.vue"
import Inbox from "./Inbox.vue"
import Message from "./Message.vue"
import Missing from "./Missing.vue"
export function create() {
const router = createRouter({
history: "memory",
routes: [
{ path: "/", component: Inbox },
{ path: "/message/:id", component: Message, props: true },
{ path: "/drafts", redirect: "/" },
{ path: "*", component: Missing },
],
})
// a guard: message 3 is locked, the reader is sent back to the inbox
router.beforeEach((to) => (to.params.id === "3" ? "/" : true))
return createApp(Shell).use(router)
}<script setup lang="ts">
import { RouterView, useRoute, useRouter } from "roots/router"
const route = useRoute()
const router = useRouter()
</script>
<template>
<Box class="router-app gap-2">
<Box class="router-app-bar flex-row items-center gap-3">
<Button label="← Back" @click="router.back()" />
<Link to="/">Inbox</Link>
<Link to="/drafts">Drafts (redirects)</Link>
<Link to="/nowhere">A missing page</Link>
<Spacer />
<Text class="router-app-path">{{ route.fullPath }}</Text>
</Box>
<RouterView />
</Box>
</template>
<style>
.router-app-bar { padding-bottom: 8px; border-bottom: 1px solid var(--roots-border); }
.router-app-path { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 0.85rem; color: var(--roots-muted); }
.router-app a.r-link-exact-active { font-weight: 700; }
</style><script setup lang="ts">
// `props: true` on the route: the :id param arrives as a prop
const props = defineProps<{ id: string }>()
</script>
<template>
<Box class="gap-1">
<Text class="bold">Message {{ props.id }}</Text>
<Text>The route is <code>/message/:id</code>, and this page got <code>id = "{{ props.id }}"</code>.</Text>
<Link :to="'/message/' + (Number(props.id) % 2 + 1)">The other message</Link>
</Box>
</template>What to try:
- Inbox → a message.
/message/:idhas a param;props: truehands it to the page as a prop. - Drafts. The route is a
redirect: guards and the page only ever see the target. - The third message. A
beforeEachguard returns another location, and the reader lands there. - A missing page.
*is the route for everything that matches nothing. - Back.
router.back()returns through the history, on every kind of history.
The routes of the example, quoted from its main.ts:
const router = createRouter({
history: "memory",
routes: [
{ path: "/", component: Inbox },
{ path: "/message/:id", component: Message, props: true },
{ path: "/drafts", redirect: "/" },
{ path: "*", component: Missing },
],
})
// a guard: message 3 is locked, the reader is sent back to the inbox
router.beforeEach((to) => (to.params.id === "3" ? "/" : true))In a real app #
An app installs its router through roots.lisp:
(:name "my-app" :entry "src/App.vue" :use ("src/router.ts"))// src/router.ts
import { createRouter } from "roots/router"
import Home from "./pages/Home.vue"
export default createRouter({
routes: [
{ path: "/", component: Home },
{ path: "/settings", component: () => import("./pages/Settings.vue") }, // loaded on first visit
{ path: "*", component: () => import("./pages/NotFound.vue") },
],
})The default history keeps the route after #, which works on every static host and inside the native shells. history: "history" gives clean addresses and needs a server that answers them; this documentation site is built that way, with one HTML file per route.
Everything else (nested routes, aliases, scroll positions, focus after a navigation, guards in components) is in the router chapter; every export is in the roots/router reference.