
Written on 3rd September 2026 by Carter Phan.
4 cách phổ biến để quyết định HTML được render ở đâu và khi nào trong ứng dụng web, đặc biệt với Next.js.
Core question:
Ai render UI, và render vào thời điểm nào?
Render time
│
┌────────────┼────────────┐
│ │ │
Build Request Browser
│ │ │
SSG SSR / ISR CSR
| SSR | SSG | ISR | CSR | |
| Render | Server | Build time | Server + cache | Browser |
| HTML có sẵn? | ✅ | ✅ | ✅ | ❌/minimal |
| Data fetch | Mỗi request | Build | Theo revalidation | Browser |
| Data freshness | ⭐⭐⭐⭐ | ⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| Request latency | Cao hơn SSG/ISR | Rất thấp | Rất thấp | Initial thấp nhưng JS/API phải load |
| Server cost | Cao | Thấp | Thấp-vừa | Thấp |
| SEO | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| Phù hợp | Dynamic pages | Static content | Semi-dynamic | Interactive app |
Cách nhớ:
SSG = build một lần
SSR = request nào render request đó
ISR = build trước + regenerate sau
CSR = browser render
Server render HTML mỗi khi có request.
Browser
│
│ GET /products/123
↓
Server
│
├── Fetch DB/API
├── Render React
↓
HTML
│
↓
Browser
Ví dụ:
User A → /product/123
↓
Server render
↓
HTML A
User B → /product/123
↓
Server render
↓
HTML B
Nếu product thay đổi:
DB
↓
Request mới
↓
Server đọc data mới
↓
HTML mới
Mỗi request có thể cần:
Request
↓
Server
↓
DB/API
↓
Render
↓
Response
→ server cost và latency cao hơn static rendering.
Phù hợp khi content:
dynamic
+
request-specific
+
cần SEO
Ví dụ:
/product/:id
Product có thể thay đổi thường xuyên.
/account
/dashboard
HTML phụ thuộc user.
/search?q=laptop
Kết quả phụ thuộc query.
Render HTML tại build time, sau đó serve static HTML.
npm run build
│
↓
Fetch data
│
↓
Generate HTML
│
↓
Static files
│
↓
CDN
Runtime:
Browser
│
↓
CDN
│
↓
HTML
Không cần:
Browser
↓
Application Server
↓
Database
cho mỗi request.
Blog:
/blog/java-gc
/blog/redis-cache
/blog/kafka
Build:
/blog/java-gc → HTML
/blog/redis → HTML
/blog/kafka → HTML
Sau đó:
1 user ─┐
100 users ─┼→ CDN → static HTML
1M users ─┘
→ scale cực tốt.
HTML có sẵn:
CDN → HTML
→ rất nhanh.
Search engine nhận được content HTML.
Không cần render server mỗi request.
Ít dependency runtime hơn:
CDN
↓
HTML
Nếu API/DB backend tạm thời down thì static page vẫn có thể serve.
Data bị stale cho tới lần build tiếp theo.
Ví dụ:
10:00 build
↓
Product price = $100
10:30 DB
↓
Price = $80
10:31 user
↓
SSG page vẫn hiển thị $100
Muốn update:
new build
↓
new HTML
Vấn đề:
Nếu website có 100,000 pages thì rebuild toàn bộ có thể expensive.
Đây là lý do ISR xuất hiện.
ISR = static generation + khả năng regenerate page sau khi deploy.
Nó nằm giữa:
SSG SSR
│ │
static dynamic
│ │
└────── ISR ────────────┘
Core idea:
Generate static page
↓
Serve cached page
↓
Sau một khoảng thời gian
↓
Regenerate page
Ví dụ:
revalidate = 60 seconds
Page được generate:
10:00
Product price = $100
User request:
10:30
Page cache vẫn có:
$100
Sau khi stale:
Request
↓
Serve existing page
↓
Trigger regeneration
↓
Fetch latest data
↓
Generate new HTML
Sau regeneration:
$80
request tiếp theo nhận page mới.
Request
↓
Fetch data
↓
Render
↓
Response
Request
↓
Cached HTML
↓
Response
↓
revalidation
↓
Regenerate
SSR:
Freshness cao hơn nhưng render cost cao hơn.
ISR:
Performance tốt hơn nhưng chấp nhận một mức stale data.
Đây là distinction rất quan trọng.
Build
↓
HTML
↓
Deploy
↓
HTML không tự update
Build
↓
HTML
↓
Deploy
↓
Revalidate
↓
Regenerate
↓
New HTML
Nói ngắn:
ISR là SSG có khả năng cập nhật static page sau deployment.
Browser nhận JavaScript trước, sau đó React render UI và fetch data.
Browser
│
↓
HTML shell
│
↓
Download JS
│
↓
React
│
↓
Fetch API
│
↓
Render UI
Ví dụ:
GET /dashboard
→ <div id="root"></div>
JS loads
↓
GET /api/dashboard
↓
data
↓
React render
Rất phù hợp với:
Interactive application
+
Authentication
+
Personalized data
+
SEO không quan trọng
Ví dụ:
/dashboard
Không cần Google index:
Google SEO
↓
không quan trọng
Nhưng user cần:
click
drag
filter
sort
real-time update
→ CSR rất hợp.
Initial page:
HTML
↓
JS download
↓
JS execute
↓
API request
↓
data
↓
render
Có thể tạo:
blank/loading state
và SEO kém hơn nếu content quan trọng chỉ xuất hiện sau client-side JavaScript.
Giả sử:
Shoes store
SSG / ISR
Vì:
SEO important
content tương đối stable
traffic cao
ISR
Vì:
SEO important
product data thay đổi
traffic cao
CSR
Vì:
personalized
highly interactive
SEO không cần
Có thể:
SSR
hoặc hybrid.
Vì:
query-dependent
SEO có thể quan trọng
Thực tế production:
Không nhất thiết toàn website chỉ dùng một strategy.
Có thể:
Website
│
┌───────────────┼────────────────┐
↓ ↓ ↓
Homepage Product Dashboard
│ │ │
ISR ISR CSR
Đây thường là approach tốt hơn.
Trong Next.js hiện đại, đặc biệt App Router, boundary không đơn giản chỉ là:
SSR / SSG / ISR / CSR
mà còn liên quan đến:
Server Components
Client Components
Data Cache
Full Route Cache
Request Memoization
Router Cache
Ví dụ:
exportdefaultasyncfunctionProductPage() {constproduct=awaitfetch(...)return<Productproduct={product}/>
}
Server Component có thể render trên server.
Nếu data/cache policy cho phép, route có thể được cached/static hoặc dynamic tùy cách fetch và configuration.
Client Component:
"use client"exportfunctionAddToCart() {// browser interaction
}
→ cần browser vì có interaction/state/event handler.
Đây là misconception rất quan trọng.
"Render trên server" không đồng nghĩa với "SSR".
Ví dụ:
SSG:
Build server
↓
Render HTML
Cũng render trên server.
Nhưng:
SSR:
Request
↓
Render HTML
Khác nhau ở thời điểm render.
Khi gặp một page, hỏi:
HTML được generate khi nào?
Build
↓
HTML
→ SSG
Request
↓
HTML
→ SSR
Build
↓
HTML
↓
revalidate
↓
new HTML
→ ISR
Browser
↓
JS
↓
Render
→ CSR
Có thể nhìn theo 3 dimension:
Freshness
↑
│
SSR
│
ISR
│
SSG
│
└────────────→ Performance / Cost
Không phải absolute, nhưng giúp hình dung trade-off:
| Strategy | Freshness | Performance | Server cost | SEO |
| SSR | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| SSG | ⭐ | ⭐⭐⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐⭐ |
| ISR | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| CSR | ⭐⭐⭐⭐⭐ | ⭐⭐ initial | ⭐ | ⭐⭐ |
Page cần SEO?
│
├── No
│ ↓
│ Interactive / personalized?
│ ↓
│ CSR
│
└── Yes
│
↓
Data có thay đổi?
│
├── Rarely
│ ↓
│ SSG
│
└── Yes
│
↓
Cần fresh mỗi request?
│
├── Yes → SSR
│
└── No → ISR
Đừng chọn:
"SSR vì SEO."
Hay:
"SSG vì performance."
Cần hỏi:
1. SEO có quan trọng không?
2. Data thay đổi thường xuyên không?
3. User-specific không?
4. Chấp nhận stale data bao lâu?
5. Traffic lớn không?
6. Server rendering cost có đáng không?
7. Page có interactive nhiều không?
Sau đó mới chọn strategy.
Nếu bạn xây một blog cá nhân:
/blog
/blog/java-gc
/blog/redis
/blog/kafka
Content:
thay đổi ít
SEO quan trọng
traffic có thể cao
→ SSG
Nếu muốn publish article mới mà không rebuild toàn bộ site:
→ ISR / on-demand revalidation
Nếu:
/admin/editor
→ CSR / Client Components
Nếu:
/search?q=java
và SEO search results quan trọng:
→ có thể dùng SSR.