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.
Static Site Generation
Đã chuẩn bị sẵn
Server-Side Rendering
Nấu khi có khách
Incremental Static Regeneration
Best of both
Client-Side Rendering
Browser làm tất cả
| Metric | SSG | SSR | ISR | CSR |
|---|---|---|---|---|
| TTFB | ~10ms ✓✓ | ~400ms ✗ | ~10ms ✓✓ | ~10ms* |
| Data Freshness | Stale | Realtime | N giây | Realtime |
| SEO | ✓✓ Full | ✓✓ Full | ✓✓ Full | ✗ Kém |
| Server Load | Zero | Cao | Rất thấp | Zero |
| Build Time | Lâu hơn | Nhanh | Nhanh | Nhanh |
| Caching | CDN tự do | Khó cache | CDN + ISR | Browser |
* CSR TTFB thấp vì HTML rỗng — nhưng FCP và LCP rất cao do phải chờ JS render.
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.
getStaticProps()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
};
}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.
getServerSideProps()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,
},
};
}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.
getStaticProps() + revalidateDù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
};
}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.
useEffect() + 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} />;
}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.
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.
if (path === '/ssg/homepage') {
return res.json({
revalidated: false,
message: 'SSG thuần, không cập nhật'
});
}
Trang cần SEO không?
Data thay đổi theo từng user/request?
Data thay đổi định kỳ (phút/giờ)?
Data hầu như không bao giờ đổi?