Dynamic routes allow you to match routes with variable segments, like user IDs or product slugs.
Basic Dynamic Segments
Use :paramName syntax:
typescriptconst routes = [ { path: '/users/:id', name: 'user', component: UserDetail }, { path: '/posts/:slug', name: 'post', component: PostDetail } ]
Matches:
/users/123→{ id: '123' }/users/abc→{ id: 'abc' }/posts/my-first-post→{ slug: 'my-first-post' }
Accessing Route Params
In Composition API
vue<script setup> import { useRoute } from 'vue-router' const route = useRoute() // Access params console.log(route.params.id) // '123' </script> <template> <h1>User {{ route.params.id }}</h1> </template>
Reacting to Param Changes
When navigating between /users/1 and /users/2, the component is reused:
vue<script setup> import { watch } from 'vue' import { useRoute } from 'vue-router' const route = useRoute() const user = ref(null) // Watch for param changes watch( () => route.params.id, async (newId) => { user.value = await fetchUser(newId) }, { immediate: true } ) </script>
Multiple Parameters
typescriptconst routes = [ { // /users/123/posts/456 path: '/users/:userId/posts/:postId', component: UserPost } ]
vue<script setup> import { useRoute } from 'vue-router' const route = useRoute() // route.params = { userId: '123', postId: '456' } </script>
Optional Parameters
Add ? to make a parameter optional:
typescriptconst routes = [ { // Matches /users and /users/123 path: '/users/:id?', component: Users } ]
Repeatable Parameters
Use + or * for repeatable segments:
typescriptconst routes = [ { // /files/one/two/three → params.path = ['one', 'two', 'three'] path: '/files/:path+', // At least one segment required component: FileExplorer }, { // /docs or /docs/a/b/c path: '/docs/:sections*', // Zero or more segments component: Documentation } ]
Custom Regex Constraints
Restrict what params can match:
typescriptconst routes = [ { // Only matches numeric IDs path: '/users/:id(\\d+)', component: UserDetail }, { // Matches everything else path: '/users/:username', component: UserProfile } ]
Now /users/123 goes to UserDetail, /users/alice goes to UserProfile.
Named Routes
Always name your routes:
typescriptconst routes = [ { path: '/users/:id', name: 'user-detail', // Named route component: UserDetail } ]
vue<!-- Use name instead of path --> <router-link :to="{ name: 'user-detail', params: { id: 123 } }"> View User </router-link>
Benefits:
- No hardcoded paths scattered in code
- Automatic encoding
- Safe refactoring
Query Parameters
Query params are separate from route params:
typescript// URL: /search?q=vue&page=2 const route = useRoute() console.log(route.query.q) // 'vue' console.log(route.query.page) // '2' (always strings)
vue<!-- Link with query --> <router-link :to="{ path: '/search', query: { q: 'vue', page: 2 } }"> Search </router-link>
Props Mode
Pass route params as props (recommended):
typescriptconst routes = [ { path: '/users/:id', component: UserDetail, props: true // Pass params as props } ]
vue<!-- UserDetail.vue --> <script setup> defineProps<{ id: string }>() </script> <template> <h1>User {{ id }}</h1> </template>
The component is now decoupled from the router!