Seminar Docs / Hybrid Architecture

HYBRID
RENDERING

Lấy cảm hứng từ sự linh hoạt của giao thông Sài Gòn: lúc nhanh như cao tốc (SSG), lúc thích ứng như hẻm nhỏ (SSR), lúc khéo léo kết hợp cả hai (ISR). Không có phương pháp nào tốt nhất — chỉ có phương pháp phù hợp nhất với từng tình huống.

📦SSG

Static Site Generation

Đã chuẩn bị sẵn

🍜SSR

Server-Side Rendering

Nấu khi có khách

🍖ISR

Incremental Static Regeneration

Best of both

🛒CSR

Client-Side Rendering

Browser làm tất cả

So sánh trực tiếp — 4 phương pháp
MetricSSGSSRISRCSR
TTFB~10ms ✓✓~400ms ✗~10ms ✓✓~10ms*
Data FreshnessStaleRealtimeN giâyRealtime
SEO✓✓ Full✓✓ Full✓✓ Full✗ Kém
Server LoadZeroCaoRất thấpZero
Build TimeLâu hơnNhanhNhanhNhanh
CachingCDN tự doKhó cacheCDN + ISRBrowser

* CSR TTFB thấp vì HTML rỗng — nhưng FCP và LCP rất cao do phải chờ JS render.

Chi tiết từng phương pháp
01
📦SSG

Static Site Generation

"Phở Gói" — Đã chuẩn bị sẵn

TTFB

~10–50ms

Data

Cố định đến lần build tiếp theo

SEO

100/100

Server

Zero

Ví von thực tế

Đã gói sẵn trong bao bì. Khách đến chỉ việc xé ra dùng ngay — không cần nấu, không cần chờ. Nhanh tuyệt đối nhưng nếu recipe thay đổi, cần đóng gói lại toàn bộ.

Cơ chế hoạt động

Next.js chạy getStaticProps() một lần duy nhất lúc build. Kết quả được render thành file HTML tĩnh và lưu vào disk. Mọi request sau đó đều nhận cùng file HTML đó — từ CDN cache, không cần server compute.

HookgetStaticProps()

Dùng khi nào?

  • Trang Landing / Homepage marketing
  • Tài liệu kỹ thuật (docs)
  • Blog posts ít thay đổi
  • Bất kỳ trang nào data không đổi theo giờ

Đánh đổi

Data có thể stale cho đến lần deploy tiếp theo. Nếu có 1000 posts, build time tăng tuyến tính.

// pages/ssg/homepage.js
export async function getStaticProps() {
  const posts = await fetch(BACKEND + '/api/posts');
  return {
    props: { posts },
    // Không có revalidate → thuần SSG
  };
}
02
🍜SSR

Server-Side Rendering

"Phở Vỉa Hè" — Nấu khi có khách

TTFB

~200–800ms

Data

Luôn mới nhất

SEO

100/100

Server

Cao

Ví von thực tế

Khách gọi mới bắt đầu trụng bánh, thái thịt. Tô phở nóng hổi nhất — data mới nhất từ database — nhưng khách phải đứng đợi. Server bận liên tục.

Cơ chế hoạt động

Next.js chạy getServerSideProps() trên server cho mỗi HTTP request. Server fetch data từ Express backend, render HTML hoàn chỉnh, rồi gửi về browser. Không có cache giữa các request trừ khi tự cấu hình.

HookgetServerSideProps()

Dùng khi nào?

  • Trang cần user-specific data (dashboard, profile)
  • Kết quả search realtime
  • Trang cần data cực kỳ fresh (giá cổ phiếu, tin tức breaking)
  • A/B testing dựa trên cookies

Đánh đổi

TTFB cao hơn SSG vì server phải fetch + render trước khi response. Server bị tải cao khi traffic lớn.

// pages/ssr/homepage.js
export async function getServerSideProps(context) {
  const start = Date.now();
  const posts = await fetch(BACKEND + '/api/posts');
  return {
    props: {
      posts,
      serverRenderMs: Date.now() - start,
    },
  };
}
03
🍖ISR

Incremental Static Regeneration

"Cơm Tấm Đang Nướng" — Best of both

TTFB

~10–50ms

Data

Tối đa N giây cũ (configurable)

SEO

100/100

Server

Rất thấp

Ví von thực tế

Cơm đã nấu sẵn từ sáng. Thịt được nướng liên tục suốt ngày — background revalidation. Khách luôn được phục vụ ngay, và thịt lúc nào cũng còn ấm nóng.

Cơ chế hoạt động

Lần đầu tiên request đến một trang ISR: Next.js serve file HTML đã build sẵn (nhanh như SSG). Đồng thời, nếu đã quá thời gian revalidate, Next.js kích hoạt background regeneration — build lại HTML mới mà không block user. Request tiếp theo nhận file mới.

HookgetStaticProps() + revalidate

Dùng khi nào?

  • Blog posts (revalidate: 60s)
  • Trang danh sách sản phẩm
  • Homepage với data thay đổi vài phút một lần
  • Bất kỳ trang nào chấp nhận data cũ vài giây

Đánh đổi

User đầu tiên sau khi revalidate window hết hạn vẫn nhận HTML cũ — next user mới thấy mới. Cần webhook để force revalidate ngay khi data thay đổi.

// pages/isr/homepage.js
export async function getStaticProps() {
  const posts = await fetch(BACKEND + '/api/posts');
  return {
    props: { posts },
    revalidate: 60, // Regenerate sau mỗi 60 giây
  };
}
04
🛒CSR

Client-Side Rendering

"Tự Nấu Ăn" — Browser làm tất cả

TTFB

~10ms (HTML rỗng)

Data

Realtime (fetch mỗi lần mount)

SEO

~40–60/100

Server

Zero

Ví von thực tế

Server giao cho bạn một cái bếp trống (HTML shell). Bạn phải tự mua nguyên liệu (fetch API), tự nấu (JavaScript render), rồi mới ăn được. Tốt cho dân pro, nhưng chậm hơn và Googlebot không ăn được.

Cơ chế hoạt động

Server trả về HTML rỗng với chỉ một thẻ <div id="__next">. Browser tải toàn bộ JS bundle, React khởi tạo, useEffect chạy, fetch API được gọi, state được update, cuối cùng DOM mới render. Googlebot thường bỏ qua vì không chạy JS.

HookuseEffect() + fetch()

Dùng khi nào?

  • Admin dashboard (không cần SEO)
  • Ứng dụng cần user interaction phức tạp
  • Real-time data (chat, live feed)
  • Các trang sau login gate

Đánh đổi

FCP và LCP cao nhất trong 4 phương pháp. SEO gần như bằng 0 cho public pages. Bundle JS phải load hoàn toàn trước khi user thấy bất kỳ thứ gì.

// pages/csr/homepage.js
// Không có getStaticProps hay getServerSideProps
export default function CSRPage() {
  const [posts, setPosts] = useState([]);
  useEffect(() => {
    fetch('/api/blog-proxy')  // Qua Next.js proxy
      .then(r => r.json())
      .then(setPosts);
  }, []);
  return <BlogGrid posts={posts} />;
}
ISR Revalidation Flow — Cụ thể trong dự án

On-demand Revalidation

Khi admin tạo hoặc cập nhật bài viết qua dashboard, Express backend tự động gọi đến Next.js /api/revalidate endpoint với shared secret token. Next.js lập tức rebuild HTML cho trang ISR đó — không cần chờ revalidate interval.

1.Admin POST /api/posts (qua proxy)
2.Express save to MongoDB
3.Express → GET NEXTJS_URL/api/revalidate?path=/isr/homepage&secret=TOKEN
4.Next.js regenerate /isr/homepage HTML
5.Next request nhận HTML mới ngay lập tức

SSG được bảo vệ khỏi revalidation

Trang SSG được cố ý giữ "hóa thạch" để minh họa sự khác biệt. Revalidate endpoint chặn mọi request rebuild cho /ssg/homepage.

// pages/api/revalidate.js
if (path === '/ssg/homepage') {
return res.json({
revalidated: false,
message: 'SSG thuần, không cập nhật'
});
}
Khi nào dùng gì? — Decision Tree
01

Trang cần SEO không?

Không: → CSR (admin dashboard, user pages)
02

Data thay đổi theo từng user/request?

Có: → SSR (profile, personalised feed)
03

Data thay đổi định kỳ (phút/giờ)?

Không: → SSG (landing page, docs tĩnh)Có: → ISR (blog, product list)
04

Data hầu như không bao giờ đổi?

Có: → SSG + manual redeploy khi cần
Built at: 2026-05-05T07:37:30.572Z · Trang này dùng SSG (getStaticProps, không có revalidate)