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

[Lv3] Thách thức triển khai SSR và giải pháp

Các vấn đề phổ biến khi làm SSR và cách giải quyết: Hydration Mismatch, biến môi trường, tương thích thư viện bên thứ ba, hiệu năng và kiến trúc deploy.


Tình huống phỏng vấn

Câu hỏi: Khi triển khai SSR, bạn gặp khó khăn gì và giải quyết như thế nào?

Nhà tuyển dụng thường đánh giá:

  1. Kinh nghiệm thực tế: đã từng làm SSR trong dự án thật hay chưa.
  2. Tư duy giải quyết vấn đề: tìm nguyên nhân gốc và ưu tiên xử lý.
  3. Độ sâu kỹ thuật: rendering, hydration, cache, deploy.
  4. Best practices: giải pháp bền vững, dễ bảo trì, đo lường được.

Thách thức 1: Hydration Mismatch

Mô tả vấn đề

Cảnh báo phổ biến:

[Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content.

Nguyên nhân thường gặp:

  • Output khác nhau giữa server render và client render
  • Dùng API chỉ có trên browser trong đường SSR (window, document, localStorage)
  • Giá trị không xác định (Date.now(), Math.random())

Giải pháp

Cách A: dùng ClientOnly

<template>
<div>
<h1>Nội dung SSR</h1>
<ClientOnly>
<BrowserOnlyComponent />
<template #fallback>
<div>Đang tải...</div>
</template>
</ClientOnly>
</div>
</template>

Cách B: đặt guard phía client

<script setup lang="ts">
const ua = ref('');

onMounted(() => {
if (process.client) {
ua.value = window.navigator.userAgent;
}
});
</script>

Thông điệp chính: output SSR phải deterministic; logic chỉ-trên-browser phải tách rõ sang client.


Thách thức 2: Biến môi trường

Mô tả vấn đề

  • Secret phía server có thể bị lộ ra client.
  • Dùng process.env lung tung làm cấu hình khó theo dõi.

Giải pháp

Tách bằng runtime config:

// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
apiSecret: process.env.API_SECRET,
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE,
},
},
});
// sử dụng
const config = useRuntimeConfig();
const apiBase = config.public.apiBase; // client + server
const secret = config.apiSecret; // chỉ server

Thông điệp chính: secret chỉ để ở server; config public đặt rõ trong block public.


Thách thức 3: Thư viện bên thứ ba không hỗ trợ SSR

Mô tả vấn đề

  • Một số package chạm vào DOM trong quá trình SSR.
  • Hậu quả: lỗi build/runtime hoặc hydration mismatch.

Giải pháp

  1. Chỉ load thư viện ở client (plugin .client.ts)
  2. Import động trong context client
  3. Cân nhắc thư viện thay thế có SSR support
let chartLib: any;
if (process.client) {
chartLib = await import('chart.js/auto');
}

Thông điệp chính: cô lập nguyên nhân trước, sau đó mới chọn client-isolation hay đổi thư viện.


Mô tả vấn đề

  • Auth trong SSR cần đọc cookie ở server.
  • Header cần đồng nhất giữa client, SSR, và API.

Giải pháp

const token = useCookie('access_token');

const { data } = await useFetch('/api/me', {
headers: process.server
? useRequestHeaders(['cookie'])
: undefined,
credentials: 'include',
});

Thông điệp chính: request SSR không được mất auth context.


Thách thức 5: Timing khi tải bất đồng bộ

Mô tả vấn đề

  • Nhiều component cùng tải một dữ liệu.
  • Request trùng lặp và loading state không nhất quán.

Giải pháp

  • Dùng key thống nhất cho deduplication
  • Dùng shared composable để trung tâm hóa truy cập data
  • Tách rõ initial load và user action
const { data, refresh } = await useFetch('/api/products', {
key: 'products-list',
lazy: false,
server: true,
});

Thông điệp chính: trung tâm hóa data flow, không fetch lặp lại ở từng component.


Thách thức 6: Hiệu năng và tải server

Mô tả vấn đề

  • SSR tăng CPU và I/O.
  • Khi tải cao, TTFB tăng.

Giải pháp

  1. Cache với Nitro
  2. Tối ưu query DB
  3. Tách SSR/CSR theo mục tiêu SEO
  4. Đặt CDN đúng cách
export default defineCachedEventHandler(
async () => await getProductsFromDB(),
{ maxAge: 60 * 10, swr: true },
);

Thông điệp chính: performance là bài toán kiến trúc, không chỉ là frontend detail.


Thách thức 7: Xử lý lỗi và 404

Mô tả vấn đề

  • ID động có thể không hợp lệ.
  • Nếu 404 semantic sai, SEO có thể index trang lỗi.

Giải pháp

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

Bổ sung:

  • error.vue để hiển thị lỗi rõ ràng
  • Trang lỗi đặt noindex, nofollow

Thông điệp chính: HTTP status, UX và SEO phải nhất quán.


Thách thức 8: Browser-only APIs

Mô tả vấn đề

  • SSR context không có window/document.
  • Truy cập trực tiếp sẽ gây runtime error.

Giải pháp

const width = ref<number | null>(null);

onMounted(() => {
width.value = window.innerWidth;
});

Hoặc guard:

if (process.client) {
localStorage.setItem('theme', 'dark');
}

Thông điệp chính: API của browser chỉ dùng trong pha client đã được bảo bọc.


Thách thức 9: Memory leak trên server

Mô tả vấn đề

  • Node process chạy lâu bị tăng memory liên tục.
  • Thường do global mutable state, timer/listener không cleanup.

Giải pháp

  1. Không giữ state theo request trong biến global
  2. Cleanup listener/interval đầy đủ
  3. Theo dõi bằng heap snapshot và process.memoryUsage()
setInterval(() => {
const mem = process.memoryUsage();
console.log('rss', mem.rss);
}, 60_000);

Thông điệp chính: leak trong SSR là rủi ro vận hành và cả bảo mật.


Thách thức 10: Script quảng cáo và tracking

Mô tả vấn đề

  • Script bên thứ ba có thể block main thread hoặc phá hydration.
  • CLS/FID/INP xấu đi.

Giải pháp

  • Load script bất đồng bộ và tiêm gần cuối trang
  • Đặt sẵn khung cho ad để tránh layout shift
  • UI quan trọng không phụ thuộc tracking
useHead({
script: [
{ src: 'https://example.com/tracker.js', async: true, tagPosition: 'bodyClose' },
],
});

Thông điệp chính: monetization không được đánh đổi rendering stability.


Thách thức 11: Kiến trúc deploy (SSR vs SPA)

Mô tả vấn đề

  • SPA deploy static, đơn giản.
  • SSR cần compute layer, observability, và quản lý process.

So sánh

Khía cạnhSPA (Static)SSR (Node/Edge)
Hạ tầngStorage + CDNCompute + CDN
Vận hànhRất đơn giảnĐộ phức tạp trung bình
Chi phíThấpCao hơn do chi phí compute
Giám sátÍtLogs, metrics, memory, cold start

Khuyên nghị

  1. Dùng PM2 hoặc container để tăng độ ổn định
  2. Cấu hình CDN và Cache-Control đúng
  3. Có staging và load test trước production
  4. Định nghĩa error budget và alerting

Thông điệp chính: SSR không chỉ là render, mà là kiến trúc vận hành.


Tổng kết cho phỏng vấn

Câu trả lời mẫu (30-45 giây):

Khi làm SSR, tôi chia vấn đề thành bốn nhóm: render deterministic để tránh hydration mismatch, tách biệt rõ config server/client, tối ưu performance bằng deduplication-cache-splitting, và đảm bảo vận hành ổn định với xử lý lỗi, memory monitoring, và kiến trúc deploy phù hợp.

Checklist:

  • ✅ Nêu ít nhất một vấn đề cụ thể và nguyên nhân
  • ✅ Trình bày giải pháp kỹ thuật rõ ràng
  • ✅ Kết nối tác động đến SEO/performance/vận hành
  • ✅ Chốt bằng bối cảnh dự án thực tế