Skip to the contentSkip to the navigation
RootsGitHubMenu

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 small app with a routerstatic previewdemos/router-app
main.ts
// 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)
}
Shell.vue
<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>
Message.vue
<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/:id has a param; props: true hands 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 beforeEach guard 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:

The routes
  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.