Dynamic routes
Each blog post has its own address: /posts/1, /posts/2 and so on. You cannot create a file for every post, so part of the address becomes a parameter, a variable whose value comes from the address. In a file name a parameter is written in square brackets.
The post page
Create the page with a command:
npx nuxi add page "posts/[id]"The file app/pages/posts/[id].vue appears. It serves every address of the form /posts/anything. Replace its content:
<!-- app/pages/posts/[id].vue -->
<script setup lang="ts">
const route = useRoute()
</script>
<template>
<section>
<h2>Post number {{ route.params.id }}</h2>
</section>
</template>useRoute() returns the current route, and route.params.id is the value of the id parameter. Open /posts/7 and the page shows "Post number 7".
Finding a post by number
While posts are kept in an array, find the right one:
<!-- app/pages/posts/[id].vue -->
<script setup lang="ts">
const posts = [
{ id: 1, title: 'First post', text: 'Hello, world!' },
{ id: 2, title: 'Learning Nuxt', text: 'Pages from files.' }
]
const route = useRoute()
const post = computed(() => posts.find((p) => p.id === Number(route.params.id)))
</script>
<template>
<article>
<template v-if="post">
<h2>{{ post.title }}</h2>
<p>{{ post.text }}</p>
</template>
<p v-else>Post not found.</p>
</article>
</template>At /posts/2 the post "Learning Nuxt" appears, and at /posts/99 you see "Post not found."
Linking to a post
In the list app/pages/posts/index.vue, turn the titles into links:
<!-- app/pages/posts/index.vue -->
<h3>
<NuxtLink :to="`/posts/${post.id}`">{{ post.title }}</NuxtLink>
</h3>The address is built from the post number. Clicking a title opens its page.
Other kinds of parameters
| File | Matches addresses | Parameter value |
|---|---|---|
posts/[id].vue | /posts/5 | '5' |
[category]/[slug].vue | /vue/props | two parameters |
archive/[[year]].vue | /archive and /archive/2024 | the parameter is optional |
docs/[...slug].vue | /docs/a/b/c | the array ['a', 'b', 'c'] |
Double brackets make a parameter optional, and the ellipsis collects all remaining parts of the address.
Validating a parameter
To open the page only for numbers, add a check to definePageMeta:
<!-- app/pages/posts/[id].vue -->
<script setup lang="ts">
definePageMeta({
validate: (route) => /^\d+$/.test(String(route.params.id))
})
</script>If the function returns false, Nuxt shows a 404 error: the address /posts/abc no longer opens the post page.
Common mistake
Parameters from the address are always strings. The comparison p.id === route.params.id never matches, because 1 and '1' are different values for ===. Convert the parameter to a number before comparing: Number(route.params.id).