Dynamic Routes and Navigation
Route parameters, reading them reactively, and navigating from code with the router instead of a template link.
2 min read
Most real applications need routes that aren't fixed strings — a product page at /products/42, a user profile at /users/ada. Vue Router handles this with dynamic segments in the route path.
Defining a dynamic segment
// src/router/index.js
const routes = [
{ path: "/products/:id", component: ProductPage },
];Any path prefixed with : is a parameter — /products/42 matches this route with id set to "42".
Reading the parameter
<!-- ProductPage.vue -->
<script setup>
import { useRoute } from "vue-router";
import { computed } from "vue";
const route = useRoute();
const productId = computed(() => route.params.id);
</script>
<template>
<p>Showing product #{{ productId }}</p>
</template>useRoute() returns the current route as a reactive object — a composable, the same pattern ref/computed follow, just specific to routing. Wrapping route.params.id in a computed() (rather than reading it once) matters because navigating from /products/42 to /products/43 reuses the same ProductPage component instance instead of destroying and recreating it, since Vue Router recognizes it's still the same matched route. Reading route.params.id reactively is what makes the displayed product actually update when only the ID changes.
<script setup>
import { useRoute } from "vue-router";
import { watch } from "vue";
const route = useRoute();
watch(
() => route.params.id,
async (newId) => {
console.log("Fetch data for product", newId);
},
{ immediate: true }
);
</script>This is the same watcher pattern from earlier in the course, applied to route params specifically — a very common real-world use of watch().
Navigating from code
<RouterLink> covers navigation triggered directly by a click. For navigation that happens as a result of other logic — after a form submits successfully, after a timed redirect — use the useRouter() composable's push method:
<script setup>
import { useRouter } from "vue-router";
const router = useRouter();
async function handleSubmit() {
// ...save the form...
router.push("/thank-you");
}
</script>Note the naming: useRoute() (singular) gives you the current route's data; useRouter() (plural) gives you the router instance itself, used for imperative actions like push, replace, and back. Mixing these up is a common typo-level bug worth watching for.
router.push({ path: "/products", query: { sort: "price" } });
router.push({ name: "product-detail", params: { id: 42 } });push accepts either a plain path string or an object describing the destination — including a named route (if you gave your route a name in its definition) plus its params, which avoids manually building path strings by hand and is generally the more maintainable style as an app's routes grow.