Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Route params and navigation

Every blog post has its own page: /posts/1, /posts/2 and so on. A route per post is impossible, so the address declares a param — a changing part that starts with a colon. The component reads it and loads the right post.

A route with a param

Add a route to src/router/index.js:

// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import AboutView from '../views/AboutView.vue'
import PostView from '../views/PostView.vue'
 
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    { path: '/', name: 'home', component: HomeView },
    { path: '/about', name: 'about', component: AboutView },
    { path: '/posts/:id', name: 'post', component: PostView },
  ],
})
 
export default router

The address /posts/7 matches this route, and the id param gets the value '7' — always a string.

Reading the param

Create src/views/PostView.vue. The useRoute function returns the current route:

<!-- src/views/PostView.vue -->
<script setup>
import { useRoute } from 'vue-router'
import { useFetch } from '../composables/useFetch.js'
 
const route = useRoute()
const { data: post, loading, error } = useFetch(
  `https://jsonplaceholder.typicode.com/posts/${route.params.id}`,
)
</script>
 
<template>
  <p v-if="loading">Loading...</p>
  <p v-else-if="error">Post not found.</p>
  <article v-else>
    <h1>{{ post.title }}</h1>
    <p>{{ post.body }}</p>
  </article>
</template>

Open http://localhost:5173/posts/1 — the title and text of the first post appear. The useFetch composable comes from the data fetching lesson.

In src/components/PostList.vue turn the titles into links:

<!-- src/components/PostList.vue, template fragment -->
<li v-for="post in posts" :key="post.id">
  <RouterLink :to="{ name: 'post', params: { id: post.id } }">
    {{ post.title }}
  </RouterLink>
</li>

Clicking a title on the home page opens the post page. A string works too: :to="'/posts/' + post.id", but a link by name does not depend on the address format.

Sometimes you navigate not on a link click but after an action — for example, returning to the list. That is what useRouter is for:

<script setup>
import { useRouter } from 'vue-router'
 
const router = useRouter()
 
function goHome() {
  router.push({ name: 'home' })
}
</script>
 
<template>
  <button @click="goHome">Back to posts</button>
</template>

The button opens the home page.

CallWhat it does
router.push(...)Navigates with a new history entry
router.replace(...)Navigates replacing the current entry
router.back()Goes one step back, like the browser button

Query params

The part of the address after ? is available in route.query: for /posts?page=2, route.query.page is '2'.

Common mistake

Going from /posts/1 to /posts/2 with a "Next post" link and seeing the old text. The router does not recreate the component when only the param changes — PostView stays the same, and its loading does not run again. Watch the param with watch(() => route.params.id, ...) and load the post again, or add a key in App.vue: <RouterView :key="$route.fullPath" /> — then the component is recreated for every new address.

Thanks for reading!

Donate to the site — we work hard for you.