# Nuxt 3: SSR và sinh trang tĩnh, hướng dẫn đầy đủ
> Làm chủ SSR và sinh trang tĩnh với Nuxt 3. Từ useFetch đến route rules, tìm hiểu cách tối ưu hiệu năng các ứng dụng Vue.js.
- Published: 2026-02-11
- Updated: 2026-04-28
- Author: SharpSkill
- Tags: nuxt 3, vue.js, ssr, sinh trang tĩnh, hiệu năng web
- Reading time: 12 min
---
Nuxt 3 thay đổi cách xây dựng các ứng dụng Vue.js bằng việc cung cấp nhiều chế độ render phù hợp với các tình huống sử dụng khác nhau. Từ Server-Side Rendering (SSR) đến sinh trang tĩnh và render lai, framework mang lại tính linh hoạt đáng kể để tối ưu hiệu năng và SEO.
> **Yêu cầu cần có**
>
> Hướng dẫn này giả định người đọc đã có kiến thức cơ bản về Vue 3 và Composition API. Việc quen thuộc với khái niệm render phía máy chủ sẽ hữu ích nhưng không bắt buộc, vì các kiến thức nền tảng được giải thích xuyên suốt bài.
## Hiểu các chế độ render của Nuxt 3
Trước khi đi sâu vào code, điều thiết yếu là hiểu sự khác biệt giữa các chế độ render hiện có. Mỗi chế độ đáp ứng những nhu cầu cụ thể về hiệu năng, SEO và trải nghiệm người dùng.
SSR (Server-Side Rendering) sinh HTML trên máy chủ cho mỗi yêu cầu. Sinh trang tĩnh (SSG) tạo trước toàn bộ trang vào thời điểm build. Chế độ lai cho phép kết hợp các phương pháp này theo từng trang.
```typescript
// nuxt.config.ts
// Cấu hình các chế độ render khác nhau
export default defineNuxtConfig({
// SSR bật mặc định (khuyến nghị cho SEO)
ssr: true,
// Sinh trang tĩnh: render trước tất cả các trang
// Sử dụng 'npm run generate' để build
// target: 'static', // Cú pháp Nuxt 2
// Chế độ lai: cấu hình theo từng route
routeRules: {
// Trang chủ: render trước và đưa vào cache
'/': { prerender: true },
// Blog: sinh trang tĩnh
'/blog/**': { prerender: true },
// Dashboard: chỉ render phía client
'/dashboard/**': { ssr: false },
// API: không pre-render
'/api/**': { prerender: false }
}
})
```
Cấu hình này thể hiện sức mạnh của chế độ lai: mỗi phần của ứng dụng dùng chế độ render phù hợp nhất với nhu cầu của mình.
## Lấy dữ liệu với useFetch và useAsyncData
Nuxt 3 cung cấp hai composable chính để lấy dữ liệu theo cách isomorphic. Các composable này hoạt động cả phía máy chủ lẫn phía client, với việc quản lý hydrat hóa tự động.
`useFetch` là một wrapper quanh `useAsyncData` giúp đơn giản hóa các lời gọi HTTP. `useAsyncData` mang lại khả năng kiểm soát cao hơn trong các tình huống nâng cao.
```vue
Đang tải bài viết...
{{ article.title }}
```
Với những tình huống cần kiểm soát nhiều hơn, `useAsyncData` cho phép thực thi bất kỳ hàm bất đồng bộ nào.
```vue
```
Các composable này tránh tình trạng fetch hai lần: dữ liệu lấy được ở máy chủ được tuần tự hóa vào payload HTML và tái sử dụng trong quá trình hydrat hóa ở phía client.
## Tùy chỉnh SSR bằng server hooks
SSR của Nuxt 3 có thể được tùy chỉnh thông qua các server hook. Các hook này cho phép can thiệp vào nhiều giai đoạn của chu trình render để thay đổi hành vi mặc định.
```typescript
// server/plugins/render-hooks.ts
// Plugin máy chủ để tùy chỉnh quá trình render SSR
export default defineNitroPlugin((nitroApp) => {
// Hook được thực thi trước khi render mỗi trang
nitroApp.hooks.hook('render:html', (html, { event }) => {
// Chèn script hoặc metadata
html.head.push(`
`)
})
// Hook quản lý cache render
nitroApp.hooks.hook('render:response', (response, { event }) => {
// Thêm header cache tùy chỉnh
const path = event.path
if (path.startsWith('/blog/')) {
// Cache dài cho bài viết blog
response.headers['Cache-Control'] = 'public, max-age=3600, s-maxage=86400'
} else if (path.startsWith('/api/')) {
// Không cache cho API
response.headers['Cache-Control'] = 'no-store'
}
})
})
```
> **Hiệu năng SSR**
>
> Hook `render:response` lý tưởng để triển khai các chiến lược cache HTTP. Việc kết hợp SSR với một CDN tôn trọng các header Cache-Control cho phép phục vụ các trang đã render trước trong khi vẫn duy trì khả năng vô hiệu hóa cache.
## Sinh trang tĩnh với nuxt generate
Sinh trang tĩnh xây dựng tất cả các trang trước thời điểm build. Cách tiếp cận này lý tưởng cho các website có nội dung ổn định như blog, tài liệu hoặc trang marketing.
Với các route động, Nuxt cần biết tất cả URL cần sinh. Hook `prerender:routes` cho phép định nghĩa các route này theo cách lập trình.
```typescript
// nuxt.config.ts
// Cấu hình đầy đủ cho sinh trang tĩnh
export default defineNuxtConfig({
// Bật sinh trang tĩnh
nitro: {
prerender: {
// Bật quét link tự động
crawlLinks: true,
// Các route luôn cần đưa vào
routes: ['/', '/about', '/contact'],
// Bỏ qua một số route
ignore: ['/admin', '/api']
}
},
hooks: {
// Hook để sinh các route động
async 'prerender:routes'(ctx) {
// Lấy bài viết từ API hoặc DB
const articles = await fetch('https://api.example.com/articles')
.then(res => res.json())
// Thêm các route bài viết
for (const article of articles) {
ctx.routes.add(`/blog/${article.slug}`)
}
// Lấy danh mục
const categories = await fetch('https://api.example.com/categories')
.then(res => res.json())
for (const category of categories) {
ctx.routes.add(`/category/${category.slug}`)
}
}
}
})
```
Với các dự án có nhiều trang, crawler tự động có thể không đủ. Đây là cách tiếp cận chắc chắn hơn với một file cấu hình riêng.
```typescript
// server/utils/generate-routes.ts
// Tiện ích sinh danh sách route động
import { prisma } from './prisma'
export async function getAllStaticRoutes(): Promise {
const routes: string[] = []
// Bài viết blog
const articles = await prisma.article.findMany({
where: { published: true },
select: { slug: true, category: { select: { slug: true } } }
})
for (const article of articles) {
routes.push(`/blog/${article.category.slug}/${article.slug}`)
}
// Trang sản phẩm
const products = await prisma.product.findMany({
where: { active: true },
select: { slug: true }
})
for (const product of products) {
routes.push(`/products/${product.slug}`)
}
// Trang tag
const tags = await prisma.tag.findMany({
select: { slug: true }
})
for (const tag of tags) {
routes.push(`/tags/${tag.slug}`)
}
return routes
}
```
## Render lai với routeRules
Render lai là tính năng nổi bật của Nuxt 3. Nó cho phép định nghĩa các quy tắc render khác nhau cho từng route, kết hợp những điểm mạnh của SSR và SSG.
```typescript
// nuxt.config.ts
// Cấu hình nâng cao cho render lai
export default defineNuxtConfig({
routeRules: {
// Trang marketing: render trước và cache lâu dài
'/': { prerender: true },
'/pricing': { prerender: true },
'/features/**': { prerender: true },
// Blog: ISR (Incremental Static Regeneration)
// Tái xác thực mỗi giờ
'/blog/**': {
isr: 3600,
prerender: true
},
// Tài liệu: cache CDN với tái xác thực
'/docs/**': {
swr: 86400, // Stale-while-revalidate
prerender: true
},
// E-commerce: SSR với cache ngắn
'/products/**': {
ssr: true,
cache: {
maxAge: 60,
staleMaxAge: 300
}
},
// Giỏ hàng và thanh toán: chỉ phía client
'/cart': { ssr: false },
'/checkout/**': { ssr: false },
// Dashboard: chế độ SPA
'/dashboard/**': {
ssr: false,
// Tắt pre-render
prerender: false
},
// Route API: không cache mặc định
'/api/**': {
cors: true,
headers: {
'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE'
}
}
}
})
```
Cấu hình này minh họa kiến trúc điển hình của ứng dụng hiện đại: trang công khai được tối ưu cho SEO bằng SSG, trong khi các phần tương tác sử dụng render phía client.
## Tối ưu hiệu năng với cache dữ liệu
Ngoài cache trang, Nuxt 3 còn cho phép cache dữ liệu đã lấy được. Chiến lược này giảm tải cho API và cải thiện thời gian phản hồi.
```typescript
// server/api/articles/[slug].get.ts
// Endpoint API có cache dữ liệu
import { getArticleBySlug } from '~/server/utils/articles'
export default defineCachedEventHandler(
async (event) => {
const slug = getRouterParam(event, 'slug')
if (!slug) {
throw createError({
statusCode: 400,
message: 'Thiếu slug'
})
}
const article = await getArticleBySlug(slug)
if (!article) {
throw createError({
statusCode: 404,
message: 'Không tìm thấy bài viết'
})
}
return article
},
{
// Khóa cache dựa trên slug
getKey: (event) => `article-${getRouterParam(event, 'slug')}`,
// Thời gian cache: 1 giờ
maxAge: 3600,
// Stale-while-revalidate: phục vụ cache cũ trong khi cập nhật
staleMaxAge: 7200,
// Vô hiệu hóa cache theo tag
tags: ['articles']
}
)
```
Để vô hiệu hóa cache khi nội dung thay đổi, Nuxt cung cấp hệ thống tag.
```typescript
// server/api/articles/[slug].put.ts
// Cập nhật bài viết kèm vô hiệu hóa cache
import { updateArticle } from '~/server/utils/articles'
export default defineEventHandler(async (event) => {
const slug = getRouterParam(event, 'slug')
const body = await readBody(event)
// Cập nhật bài viết
const article = await updateArticle(slug, body)
// Vô hiệu hóa cache cho bài viết này
await useStorage('cache').removeItem(`nitro:handlers:article-${slug}`)
// Hoặc vô hiệu hóa theo tag (toàn bộ bài viết)
// await useStorage('cache').clear('articles')
return article
})
```
> **Cache phân tán**
>
> Trong môi trường sản xuất với nhiều instance, cache trong bộ nhớ là không đủ. Khuyến nghị cấu hình Redis hoặc một hệ thống phân tán khác qua cấu hình Nitro để đảm bảo tính nhất quán giữa các instance.
## Quản lý SEO và metadata
SSR cho phép tối ưu SEO bằng cách sinh metadata ở phía máy chủ. Nuxt 3 cung cấp nhiều cách để quản lý meta tag một cách động.
```vue
```
Đối với trang tĩnh, metadata có thể được định nghĩa trực tiếp trong component.
```vue
```
## Triển khai và lưu ý ở môi trường sản xuất
Lựa chọn triển khai phụ thuộc vào chế độ render được dùng. Dưới đây là các tùy chọn chính và cấu hình của chúng.
```typescript
// nuxt.config.ts
// Cấu hình cho các môi trường triển khai khác nhau
export default defineNuxtConfig({
nitro: {
// Preset theo nền tảng đích
// preset: 'vercel', // Vercel
// preset: 'netlify', // Netlify
// preset: 'cloudflare-pages', // Cloudflare
// preset: 'node-server', // Node.js cổ điển
// Cấu hình cho Node.js trong môi trường sản xuất
preset: 'node-server',
// Nén phản hồi
compressPublicAssets: true,
// Cấu hình kho lưu trữ cache
storage: {
cache: {
driver: 'redis',
url: process.env.REDIS_URL
}
}
},
// Biến môi trường runtime
runtimeConfig: {
// Bí mật (không được lộ ra phía client)
apiSecret: process.env.API_SECRET,
// Cấu hình công khai
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api'
}
}
})
```
Với triển khai tĩnh, lệnh `npm run generate` tạo ra thư mục `.output/public` sẵn sàng để triển khai trên bất kỳ host file tĩnh nào.
```bash
# Sinh trang tĩnh
npm run generate
# Nội dung của .output/public có thể được triển khai trên:
# - Vercel (tự động phát hiện)
# - Netlify (tự động cấu hình)
# - GitHub Pages
# - S3 + CloudFront
# - Bất kỳ CDN hoặc máy chủ file tĩnh nào
```
## Kết luận
Nuxt 3 mang lại tính linh hoạt vượt trội để render các ứng dụng Vue.js. Việc lựa chọn giữa SSR, SSG và render lai phụ thuộc vào nhu cầu cụ thể của từng dự án.
**Điểm chính:**
✅ **SSR**: lý tưởng cho nội dung động cần SEO tốt (e-commerce, trang tin tức)
✅ **SSG**: hoàn hảo cho nội dung ổn định (blog, tài liệu, trang marketing)
✅ **Lai**: phương án tốt nhất cho ứng dụng phức tạp với nhu cầu đa dạng
✅ **useFetch/useAsyncData**: hydrat hóa tự động và quản lý cache
✅ **routeRules**: cấu hình chi tiết cho hành vi của từng route
✅ **Caching**: nhiều chiến lược để tối ưu hiệu năng trong môi trường sản xuất
Việc kết hợp render lai với một chiến lược cache được cân nhắc kỹ giúp xây dựng các ứng dụng hiệu năng cao, được tối ưu cho SEO mà vẫn giữ được tính tương tác của Single Page Applications.
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/vi/blog/vue-nuxt/nuxt-3-ssr-static-generation