Chúng ta tiếp tục với Bài 13 – Dynamic Route và Slug URL trong NuxtJS. Đây là một phần cực kỳ quan trọng để bạn xây dựng blog, trang chi tiết sản phẩm, hồ sơ người dùng… với đường dẫn thân thiện và dễ SEO.
1. Dynamic Route là gì?
Dynamic Route (route động) cho phép bạn tạo một template chung cho nhiều trang khác nhau, ví dụ:
/blog/hello-nuxt
/blog/vue-3-la-gi
/product/ao-thun-dep
Chỉ cần 1 file duy nhất, Nuxt tự động khớp URL → render nội dung đúng.
2. Tạo dynamic route với [slug].vue
Trong thư mục pages/blog/
, tạo file:
pages/
└── blog/
└── [slug].vue
Ví dụ hiển thị bài viết theo slug:
<script setup>
const route = useRoute()
const { data: post } = await useAsyncData(() =>
queryContent('/blog').where({ _path: `/blog/${route.params.slug}` }).findOne()
)
if (!post.value) {
throw createError({ statusCode: 404, statusMessage: 'Không tìm thấy bài viết' })
}
</script>
<template>
<div v-if="post">
<h1>{{ post.title }}</h1>
<ContentRenderer :value="post" />
</div>
</template>
3. Tạo slug thân thiện trong Markdown
Trong content/
:
content/
└── blog/
├── hello-nuxt.md
└── vue-co-ban.md
Trong hello-nuxt.md
:
---
title: Hello Nuxt
description: Giới thiệu nhanh về NuxtJS
date: 2025-07-07
---
# Hello Nuxt
Bài viết đầu tiên...
→ File tên hello-nuxt.md
→ tương ứng route /blog/hello-nuxt
4. Lấy slug từ nhiều segment
Nếu bạn muốn cấu trúc như /blog/vue/hello-nuxt
, thì dùng:
pages/blog/[...slug].vue
→ route.params.slug
lúc này là array: ['vue', 'hello-nuxt']
Bạn cần nối lại:
const fullPath = '/blog/' + route.params.slug.join('/')
5. Hiển thị 404 nếu không có bài viết
if (!post.value) {
throw createError({ statusCode: 404, statusMessage: 'Không tìm thấy bài viết' })
}
→ Nuxt sẽ tự redirect về trang 404.
Bạn có thể tùy chỉnh giao diện 404 trong error.vue
.
6. Preview SEO-friendly URLs
URL | Bài viết | File |
---|---|---|
/blog/vue-co-ban | Vue cơ bản | vue-co-ban.md |
/blog/hoc-nuxt-chi-tiet | Học NuxtJS | hoc-nuxt-chi-tiet.md |
→ Không cần định nghĩa từng route thủ công như với Vue CLI
7. Optional: Tạo slug
riêng trong frontmatter
Nếu muốn chủ động kiểm soát slug, bạn có thể làm:
---
title: Bài viết tùy chọn
slug: gioi-thieu-nuxt
---
→ Khi đó bạn cần đổi truy vấn:
queryContent('/blog').where({ slug: route.params.slug }).findOne()
Bạn đã học được
- Tạo dynamic route với
[slug].vue
- Đọc dữ liệu tương ứng với slug từ Markdown
- Xử lý khi không tìm thấy (404)
- Cấu trúc thư mục content để có slug đẹp
- Dùng
[...slug].vue
cho URL lồng nhau
Bài tập mở rộng
- Tạo ít nhất 3 bài viết trong
content/blog/
với tên tệp slug thân thiện - Dùng
[slug].vue
hiển thị chi tiết bài viết - Thêm logic 404 nếu bài không tồn tại
- Trang
/blog
hiển thị danh sách bài + link đến chi tiết
Thảo luận