Chuyển tới nội dung chính

[Lv2] Triển khai SSR: Data Fetching và quản lý SEO Meta

Trong dự án Nuxt 3: triển khai tải dữ liệu bằng SSR và quản lý SEO Meta động để công cụ tìm kiếm index đúng các route động.


1. Trọng tâm trả lời phỏng vấn​

  1. Chiến lược data fetching: dùng useFetch/useAsyncData để preload trên server, đảm bảo HTML đầy đủ cho SEO.
  2. Meta tags động: dùng useHead hoặc useSeoMeta để tạo metadata theo từng resource.
  3. Hiệu năng: áp dụng request deduplication, cache phía server, và tách rõ trang SSR/CSR.

2. Cách dùng đúng useFetch / useAsyncData​

2.1 Tại sao data fetching trong SSR quan trọng​

Tình huống phổ biến:

  • Route động (ví dụ /products/[id]) cần dữ liệu từ API.
  • Nếu chỉ load ở client, crawler có thể thấy nội dung không đầy đủ.
  • Mục tiêu: trả về HTML đã render đầy đủ dữ liệu từ server.

Giải pháp: dùng useFetch hoặc useAsyncData trong Nuxt 3.

2.2 Ví dụ cơ bản với useFetch​

File: pages/products/[id].vue

// cách dùng cơ bản
const { data: product } = await useFetch(`/api/products/${route.params.id}`);

Các option quan trọng:

OptionMục đíchDefault
keyKhóa duy nhất để deduplicate requestauto
lazyTải trễ (không block SSR)false
serverChạy trên servertrue
defaultGiá trị fallbacknull
transformBiến đổi response trước khi sử dụng-

2.3 Ví dụ đầy đủ​

// pages/products/[id].vue
const { data: product } = await useFetch(`/api/products/${route.params.id}`, {
key: `product-${route.params.id}`, // tránh request trùng lặp
lazy: false, // SSR đợi dữ liệu
server: true, // đảm bảo chạy ở server
default: () => ({
id: null,
name: '',
description: '',
image: '',
}),
transform: (data: any) => {
// chuẩn hóa dữ liệu
return {
...data,
formattedPrice: formatPrice(data.price),
};
},
});

Vì sao các option này quan trọng:

  1. key
    • Cho phép request deduplication.
    • Cùng key -> một request hiệu lực.
  2. lazy: false
    • Server chỉ render sau khi có dữ liệu.
    • Crawler nhận được nội dung cuối.
  3. server: true
    • Fetch chạy trên đường SSR.
    • Không phụ thuộc chỉ vào client.

2.4 useAsyncData vs useFetch​

Tiêu chíuseFetchuseAsyncData
Mục đích chínhGọi APIMọi tác vụ bất đồng bộ
Mức độ tiện lợiURL/header tích hợpTự viết logic
Tình huống dùngHTTP data fetchingDB query, tổng hợp, đọc file
// useFetch: tập trung vào API
const { data } = await useFetch('/api/products/123');

// useAsyncData: logic async tùy biến
const { data } = await useAsyncData('products', async () => {
const result = await someAsyncOperation();
return result;
});

2.5 $fetch vs useFetch​

Quy tắc ngắn cho phỏng vấn:

  • $fetch cho hành động người dùng (click, submit, refresh).
  • useFetch cho tải lần đầu, đồng bộ với SSR/Hydration.

$fetch đặc điểm:

  • HTTP client thuần (ofetch)
  • Không chuyển state SSR
  • Dùng trực tiếp trong setup() dễ gây double fetch

useFetch đặc điểm:

  • Kết hợp useAsyncData + $fetch
  • Thân thiện với hydration
  • Trả về data, pending, error, refresh

So sánh:

MụcuseFetch$fetch
Chuyển state SSRCóKhông
Giá trị trả vềRef reactivePromise dữ liệu raw
Mục đích chínhTải dữ liệu ban đầu của trangTác vụ theo sự kiện
// đúng: tải ban đầu
const { data } = await useFetch('/api/user');

// đúng: hành động người dùng
const submitForm = async () => {
await $fetch('/api/submit', { method: 'POST', body: form });
};

// tránh: setup + $fetch trực tiếp
const data = await $fetch('/api/user');

3. Quản lý SEO Meta với useHead​

3.1 Tại sao cần meta tags động​

Tình huống phổ biến:

  • Trang sản phẩm và bài viết là động.
  • Mỗi URL cần title, description, og:image, canonical riêng.
  • Chia sẻ social (Open Graph/Twitter) cần động nhất.

Giải pháp: useHead hoặc useSeoMeta.

3.2 Ví dụ useHead​

useHead({
title: () => product.value?.name,
meta: [
{ name: 'description', content: () => product.value?.description },
{ property: 'og:title', content: () => product.value?.name },
{ property: 'og:image', content: () => product.value?.image },
],
link: [
{
rel: 'canonical',
href: () => `https://example.com/products/${product.value?.id}`,
},
],
});

Best practices:

  1. Truyền giá trị dạng hàm (() => ...) để metadata cập nhật theo dữ liệu.
  2. Đặt đầy đủ cấu trúc SEO: title, description, OG, canonical.
  3. Với 404, đặt noindex, nofollow.

3.3 Bản gọn với useSeoMeta​

useSeoMeta({
title: () => product.value?.name,
description: () => product.value?.description,
ogTitle: () => product.value?.name,
ogDescription: () => product.value?.description,
ogImage: () => product.value?.image,
});

4. Tình huống thực tế 1: SEO cho route động​

4.1 Bối cảnh​

Kịch bản e-commerce có nhiều trang SKU (/products/[id]).

Thách thức:

  • Nhiều URL động
  • SEO riêng cho từng URL
  • Xử lý 404 đúng
  • Tránh duplicate content

4.2 Chiến lược​

  1. Preload trên server (lazy: false, server: true)
  2. Ném lỗi 404 với createError
  3. Tạo meta và canonical động
const { data: product, error } = await useFetch(`/api/products/${id}`, {
key: `product-${id}`,
lazy: false,
server: true,
});

if (error.value || !product.value) {
throw createError({ statusCode: 404, statusMessage: 'Product not found' });
}

useSeoMeta({
title: () => `${product.value?.name} - Product`,
description: () => product.value?.description,
ogTitle: () => product.value?.name,
ogDescription: () => product.value?.description,
ogImage: () => product.value?.image,
});

4.3 Kết quả​

Trước đó:

  • Crawler thấy nội dung thiếu
  • Nhiều trang trùng metadata
  • 404 không nhất quán

Sau khi làm:

  • HTML SSR đầy đủ cho crawler
  • Metadata riêng theo route
  • Xử lý lỗi nhất quán và an toàn cho SEO

5. Tình huống thực tế 2: Tối ưu hiệu năng​

5.1 Vấn đề​

SSR tăng tải cho server. Không tối ưu sẽ tăng độ trễ và chi phí.

5.2 Chiến lược​

  1. Request deduplication
const { data } = await useFetch('/api/product/123', {
key: 'product-123',
});
  1. Cache server (Nitro)
export default defineCachedEventHandler(
async (event) => {
return await getProductsFromDB();
},
{
maxAge: 60 * 60,
swr: true,
},
);
  1. Tách SSR/CSR
  • Trang quan trọng cho SEO: SSR
  • Trang nội bộ không cần index: CSR
  1. Critical CSS và chiến lược assets
  • Ưu tiên CSS above-the-fold
  • Tải tài nguyên không quan trọng sau

5.3 Tác động​

Trước đó:

  • Tải server cao
  • Request lặp lại
  • Không có chiến lược cache

Sau khi làm:

  • Thời gian phản hồi tốt hơn
  • Giảm áp lực backend/DB
  • Ổn định hơn khi có tải cao

6. Câu trả lời phỏng vấn ngắn gọn​

6.1 useFetch / useAsyncData​

Tôi dùng useFetch cho tải ban đầu với key, lazy: false, server: true để đảm bảo SSR đầy đủ và HTML có ích cho index.

6.2 Meta tags động​

Tôi dùng useHead/useSeoMeta với giá trị dạng hàm để metadata cập nhật theo dữ liệu, bao gồm OG và canonical.

6.3 Hiệu năng​

Tôi kết hợp deduplication, cache server, và tách SSR/CSR để giảm TTFB mà vẫn giữ chất lượng SEO.


7. Best practices​

7.1 Data fetching​

  1. Luôn đặt key.
  2. Chọn lazy theo nhu cầu SEO.
  3. Xử lý lỗi (404/5xx) rõ ràng.

7.2 SEO Meta​

  1. Giá trị dạng hàm cho cập nhật reactive.
  2. Cấu trúc đầy đủ (title/description/OG/canonical).
  3. Bảo vệ trang lỗi với noindex, nofollow.

7.3 Hiệu năng​

  1. Dùng cache trên server.
  2. Chỉ dùng SSR ở nơi SEO cần.
  3. Giảm chi phí render bằng chiến lược CSS/assets.

8. Tổng kết phỏng vấn​

Trong Nuxt 3, tôi đã triển khai SSR data fetching và SEO Meta động để đạt hai mục tiêu: index đúng và trải nghiệm nhanh. Cách làm gồm preload trên server, metadata theo route, và tối ưu bằng deduplication, cache, và tách SSR/CSR.

Điểm chính:

  • ✅ Dùng useFetch/useAsyncData đúng cách
  • ✅ Quản lý metadata động bằng useHead/useSeoMeta
  • ✅ SEO cho route động
  • ✅ Tối ưu hiệu năng cho dự án thực tế