# Vue Router Basics

Course: [Vue.js](https://www.devtutorials.ai/learn/vue) · Section: Routing and State
Source: https://www.devtutorials.ai/learn/vue/routing-state/vue-router-basics

> Installing Vue Router, createRouter() and history modes, routes, RouterLink and RouterView, active link classes, useRouter() and useRoute(), params and query, optional, repeatable and pattern params, nested routes, named views, route props, named routes, redirects, aliases, a 404 page, and push, replace and go.

## Pages in a Single-Page App

- A **Single-Page App (SPA)** loads one HTML page, then swaps content without a full reload.
- Users still expect real addresses: `/menu`, `/dish/3`, the Back button, bookmarks.
- **Vue Router** = the official library that links addresses to components.
- When the address changes, the router shows the matching component.

**Simple lesson:** Vue Router maps addresses to components without reloading the page.

### A Real-World Example

- Meera's site has three pages: Home, Menu and About.
- Clicking a link must change the address and the page, with no reload.
- The Back button must work too.

    <nav>
    </nav>
</template>

<style>
.router-link-exact-active { font-weight: bold; color: #2f9e6e; }
</style>
`,
    "views/HomePage.vue": `<template>
    <h2>Welcome to Meera's Kitchen</h2>
</template>
`,
    "views/MenuPage.vue": `<template>
    <h2>Menu</h2>
    <p>Dosa, Idli, Vada</p>
</template>
`,
    "views/AboutPage.vue": `<template>
    <h2>About us</h2>
    <p>Family-run since 2019.</p>
</template>
`,
  }}
/>

- **What happened:** each link changed the address bar and the page below it.
- The active link turned bold and green. Back and Forward in the bar work.

### What You Will Learn

1. How to install the router and create it.
2. The three history modes.
3. How `RouterLink` and `RouterView` work.
4. How to style the active link.
5. How to read the address with `useRoute()`.
6. How to read params and query.
7. Optional, repeatable and pattern params, and a 404 page.
8. Nested routes and named views.
9. Moving by code with `push`, `replace` and `go`.

## Setting Up the Router

- Install it in a real project: `npm install vue-router`. create-vue can add it for you.
- `createRouter({ history, routes })` makes the router.
- Each route is an object: `{ path: "/menu", component: MenuPage }`.
- `app.use(router)` installs it. The router is a plugin, as you saw in **Composables, Directives and Plugins**.
- Put page components in a `views/` folder.

<figure className="vue-fig ngc-fig">
<svg viewBox="0 0 640 190" role="img" aria-labelledby="fig-vue-router" style={{ width: "100%", maxWidth: 640, height: "auto" }}>
  <title id="fig-vue-router">Animation: a click on a RouterLink changes the address to /menu. The router finds the matching route in the list and shows MenuPage inside RouterView</title>
  <defs><marker id="vrt-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0 0 L10 5 L0 10 z" style={{ fill: "var(--ngc-slate)" }} /></marker></defs>
  <rect x="20" y="20" width="200" height="40" rx="20" style={{ fill: "var(--ngc-win)", stroke: "var(--ngc-slate)", strokeWidth: 1.5 }} />
  <text x="120" y="45" textAnchor="middle" style={{ fill: "var(--ngc-slate)", fontFamily: "var(--mono)", fontSize: 13 }}>/<animate attributeName="opacity" values="1;1;0;0;1" keyTimes="0;0.2;0.21;0.97;1" dur="7s" repeatCount="indefinite" /></text>
  <text x="120" y="45" textAnchor="middle" opacity="0" style={{ fill: "var(--ngc-orange)", fontFamily: "var(--mono)", fontSize: 13, fontWeight: 700 }}>/menu<animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.2;0.21;0.97;1" dur="7s" repeatCount="indefinite" /></text>
  <rect x="250" y="20" width="170" height="150" rx="12" style={{ fill: "var(--ngc-purple-soft)", stroke: "var(--ngc-purple)", strokeWidth: 2 }} />
  <text x="335" y="42" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>routes</text>
  <g style={{ fontFamily: "var(--mono)", fontSize: 12 }}>
    <text x="265" y="72" style={{ fill: "var(--ngc-purple)" }}>/      → HomePage</text>
    <text x="265" y="100" style={{ fill: "var(--ngc-purple)" }}>/menu  → MenuPage</text>
    <text x="265" y="128" style={{ fill: "var(--ngc-purple)" }}>/about → AboutPage</text>
  </g>
  <rect x="258" y="84" width="154" height="24" rx="6" opacity="0" style={{ fill: "none", stroke: "var(--ngc-orange)", strokeWidth: 2.5 }}><animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.35;0.4;0.95;1" dur="7s" repeatCount="indefinite" /></rect>
  <rect x="450" y="20" width="170" height="150" rx="12" style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)", strokeWidth: 2 }} />
  <text x="535" y="42" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontFamily: "var(--mono)", fontSize: 13, fontWeight: 700 }}>{"<RouterView />"}</text>
  <text x="535" y="100" textAnchor="middle" style={{ fill: "var(--ngc-green)", fontFamily: "var(--sans)", fontSize: 15, fontWeight: 700 }}>HomePage<animate attributeName="opacity" values="1;1;0;0;1" keyTimes="0;0.55;0.56;0.97;1" dur="7s" repeatCount="indefinite" /></text>
  <text x="535" y="100" textAnchor="middle" opacity="0" style={{ fill: "var(--ngc-orange)", fontFamily: "var(--sans)", fontSize: 15, fontWeight: 700 }}>MenuPage<animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.55;0.56;0.97;1" dur="7s" repeatCount="indefinite" /></text>
  <path d="M220 40 H248" style={{ stroke: "var(--ngc-slate)", strokeWidth: 2 }} markerEnd="url(#vrt-arrow)" />
  <path d="M420 96 H448" style={{ stroke: "var(--ngc-slate)", strokeWidth: 2 }} markerEnd="url(#vrt-arrow)" />
  <text x="320" y="185" textAnchor="middle" style={{ fill: "var(--ngc-slate)", fontFamily: "var(--sans)", fontSize: 13 }}>Address changes → the router finds the route → RouterView shows its component</text>
</svg>
<figcaption>The address picks the route. RouterView shows the route's component.</figcaption>
</figure>

### History Modes

<div className="tscroll">

| Mode | Address looks like | Server setup needed | Use for |
|---|---|---|---|
| `createWebHistory()` | `/menu` | Yes: send every path to `index.html` | Most real websites |
| `createWebHashHistory()` | `/#/menu` | No | Simple static hosting |
| `createMemoryHistory()` | Not shown in the bar | No | Tests and server rendering |

</div>

With `createWebHistory()`, a real server must answer every path with `index.html`. You will set this up in **Errors, Security and Deployment**. In this lab, the preview has no real address bar, so web history is kept in memory and shown in a small bar above the app.

### RouterLink and RouterView

- `<RouterLink to="/menu">` renders an `<a>` that changes the page without a reload.
- `<RouterView />` is where the current route's component appears.
- The link to the current page gets the class `router-link-exact-active`.
- A link to a parent of the current page gets `router-link-active`. For `/menu/3`, the `/menu` link has it.

**Simple lesson:** routes in `createRouter()`, links with `RouterLink`, pages in `RouterView`.

## Params and Query

- **Dynamic segment** = a part of the path that changes, written with a colon: `/dish/:id`.
- `/dish/3` and `/dish/7` both match. The value is a **param**.
- **Query** = the part after `?`: `/menu?veg=1&sort=price`.
- `useRoute()` gives the current route: `route.params.id`, `route.query.veg`.
- `route` is reactive. Use it in `computed` or the template.
- Going from `/dish/1` to `/dish/2` **reuses** the same component. Its setup code does not run again. So read params in `computed` or `watch`, not once at the top.

</template>
`,
    "views/MenuPage.vue": `<script setup lang="ts">

const route = useRoute();
</script>

<template>
    <h2>Menu</h2>
    <p>Filter: {{ route.query.veg ? "veg only" : "all dishes" }}</p>
</template>
`,
    "views/DishPage.vue": `<script setup lang="ts">

const route = useRoute();
const id = computed(() => Number(route.params.id));
</script>

<template>
    <h2>Dish number {{ id }}</h2>
    <p>Spicy: {{ route.query.spicy ?? "not asked" }}</p>
</template>
`,
  }}
/>

Going from `/dish/1` to `/dish/3` reuses the same component. Code in `<script setup>` does not run again. Read params in `computed`, or `watch` them. You will see more in **Navigation Guards and Advanced Routing**.

## Moving by Code

- `useRouter()` gives the router itself.
- `router.push("/menu")` goes to a page and adds a history entry.
- `router.replace("/menu")` goes without adding an entry. Back skips it.
- `router.go(-1)` goes back one step. `go(1)` goes forward.
- **Named route** = a route with a `name`. Push with `{ name: "dish", params: { id: 2 } }`. The path can change later without breaking links.

const router = useRouter();
</script>

<template>
    <button @click="router.push({ name: 'dish', params: { id: 2 } })">push dish 2</button>
    <button @click="router.replace({ name: 'dish', params: { id: 5 }, query: { from: 'offer' } })">replace with dish 5</button>
    <button @click="router.go(-1)">go(-1)</button>
    <RouterLink :to="{ name: 'home' }">Home</RouterLink>
</template>
`,
    "HomePage.vue": `<template>
    <h2>Home</h2>
</template>
`,
    "DishPage.vue": `<script setup lang="ts">

const route = useRoute();
</script>

<template>
    <h2>Dish {{ route.params.id }}</h2>
</template>
`,
  }}
/>

**Simple lesson:** `useRoute()` reads the address. `useRouter()` changes it.

Prefer named routes in links and `push`: `{ name: "dish", params: { id: 2 } }`. If the path changes from `/dish/:id` to `/menu/:id` later, every named link keeps working.

## Matching Patterns

- **Optional param:** `/menu/:category?` matches `/menu` and `/menu/drinks`.
- **Repeatable param:** `/tags/:tags+` matches `/tags/veg/spicy`. `tags` is an array.
- **Pattern param:** `/order/:id(\\d+)` matches only digits.
- **Catch-all:** `/:pathMatch(.*)*` matches anything. Put a 404 page there.
- Routes are ranked by how specific they are, not by their order.

</template>
`,
    "ShowRoute.vue": `<script setup lang="ts">

const route = useRoute();
</script>

<template>
    <p>Matched <code>{{ route.matched[0]?.path }}</code>, params: {{ JSON.stringify(route.params) }}</p>
</template>
`,
    "NotFound.vue": `<script setup lang="ts">

const route = useRoute();
</script>

<template>
    <h2>404: nothing at {{ route.path }}</h2>
</template>
`,
  }}
/>

## Nested Routes, Named Views, Props, Redirects and Aliases

- **Nested routes:** a route has `children`. Its component holds its own `<RouterView />`.
- **Named views:** several `<RouterView name="side" />` on one page, filled by `components: { default, side }`.
- **Route props:** `props: true` passes params as props, so the component does not need `useRoute()`.
- **Redirect:** `{ path: "/home", redirect: "/" }` sends one path to another.
- **Alias:** `{ path: "/menu", alias: "/food" }` shows the same page at a second address.

</template>
`,
    "views/AccountPage.vue": `<template>
    <h3>My account</h3>
    <div class="tab"><RouterView /></div>
    <aside><RouterView name="side" /></aside>
</template>

<style>
.tab { border: 1px solid #ccc; padding: 6px; margin-top: 6px; }
</style>
`,
    "views/ProfileTab.vue": `<template>
    <p>Name: Asha Rao</p>
</template>
`,
    "views/OrdersTab.vue": `<template>
    <p>Orders: 501, 502</p>
</template>
`,
    "views/OrderDetail.vue": `<script setup lang="ts">
defineProps({ id: { type: String, required: true } });
</script>

<template>
    <p>Order {{ id }}: 2 × Dosa (the id came in as a prop)</p>
</template>
`,
    "views/HelpSide.vue": `<template>
    <p>Need help with this order? Call 98765 43210.</p>
</template>
`,
  }}
/>

- **What happened:** `/` redirected to `/account`.
- The account page stayed. Only the inner `RouterView` changed between tabs.
- On order 501, a second, named view showed help on the side.

**Simple lesson:** `children` for pages inside pages, named views for several areas, `props: true` for clean components.

## TL;DR

- **Vue Router** = the official router. Links addresses to components.
- **`createRouter({ history, routes })`** = makes the router. Install with `app.use(router)`.
- **History modes** = web (`/menu`), hash (`/#/menu`), memory (no address).
- **`<RouterLink to>` / `<RouterView />`** = links without reload / where the page appears.
- **Active classes** = `router-link-active`, `router-link-exact-active`.
- **`useRoute()`** = `route.params`, `route.query`, `route.path`.
- **`useRouter()`** = `push`, `replace`, `go`.
- **Patterns** = `:id?`, `:tags+`, `:id(\d+)`, `/:pathMatch(.*)*` for 404.
- **Nested / named / props** = `children`, `<RouterView name>`, `props: true`.

## Common mistakes

<div className="tscroll">

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| `<a href>` for inner links | Normal links are fine | Use `<RouterLink to>` | The whole page reloads and state is lost |
| Forgetting `<RouterView />` | Pages appear by themselves | Add `<RouterView />` where pages go | Links change the address, the page stays blank |
| Reading params once in setup | Setup runs on every param change | Use `computed` or `watch` | The page shows the old dish |
| Web history with no server rule | It works on any host | Send every path to `index.html` | Refreshing `/menu` gives a 404 |
| No catch-all route | Unknown pages show nothing harmful | Add `/:pathMatch(.*)*` | A blank page and a warning |
| `router.push` with `path` and `params` | Params fill in the path | Use `name` with `params` | Params are ignored |
| Nested route without an inner `RouterView` | Children show by themselves | Add `<RouterView />` in the parent page | Child pages never appear |

</div>

## Check Yourself

## Exercises

## What to Remember

- Routes map paths to components. `RouterView` shows the match.
- Use `RouterLink`, never a plain `<a>`, for pages inside the app.
- `useRoute()` reads params and query. Read them in `computed`.
- `useRouter()` moves: `push`, `replace`, `go`.
- Add a catch-all 404 route, and a server rule for web history.

Next: **Navigation Guards and Advanced Routing**
