SSR
Bài 6 – Cách hoạt động của Server-Side Rendering trong Next.js
SSR – Server-Side Rendering
SSR (Server-Side Rendering) là cơ chế render trang tại server mỗi khi có request. Đây là phương pháp quan trọng khi dữ liệu thay đổi liên tục hoặc cần SEO tốt nhưng không thể cache lâu. Next.js hỗ trợ SSR ở cả Pages Router và App Router, nhưng App Router mang lại hiệu năng ổn định hơn.
1. SSR là gì?
1.1. Định nghĩa và Use Cases
SSR (Server-Side Rendering) là kỹ thuật render HTML tại server cho mỗi request:
- Dữ liệu thay đổi thường xuyên (real-time data)
- Yêu cầu xác thực (authentication required)
- SEO quan trọng với content động
- Personalized content theo user
1.2. SSR Flow trong Next.js
User Request → Server → Fetch Data → Render HTML → Send HTML → Client → Hydrate
2. SSR trong App Router
2.1. Cơ bản với fetch()
// app/news/page.tsx – SSR trong App Router
export default async function NewsPage() {
// Fetch data mới cho mỗi request
const res = await fetch("https://api.example.com/news", {
cache: "no-store" // Không cache, luôn fetch mới
});
const news = await res.json();
return (
<div className="news-container">
<h1 className="text-3xl font-bold mb-6">📰 Tin tức mới nhất</h1>
<div className="news-grid">
{news.map((article) => (
<article key={article.id} className="news-card">
<h2 className="text-xl font-semibold">{article.title}</h2>
<p className="text-gray-600 mt-2">{article.summary}</p>
<time className="text-sm text-gray-500 mt-2 block">
{new Date(article.publishedAt).toLocaleDateString('vi-VN')}
</time>
</article>
))}
</div>
</div>
);
}
2.2. SSR với Authentication
// app/dashboard/page.tsx – SSR với user authentication
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
export default async function DashboardPage() {
const cookieStore = cookies()
const token = cookieStore.get('auth-token')
if (!token) {
redirect('/login')
}
// Fetch user data với authentication
const res = await fetch("https://api.example.com/user/profile", {
cache: "no-store",
headers: {
'Authorization': `Bearer ${token.value}`
}
});
if (!res.ok) {
redirect('/login')
}
const user = await res.json();
return (
<div className="dashboard">
<h1>Welcome back, {user.name}!</h1>
<div className="user-stats">
<p>Email: {user.email}</p>
<p>Member since: {new Date(user.createdAt).toLocaleDateString()}</p>
</div>
</div>
);
}
2.3. SSR với Dynamic Parameters
// app/products/[id]/page.tsx – SSR cho product detail
export default async function ProductPage({
params
}: {
params: { id: string }
}) {
// Fetch product data theo ID
const res = await fetch(`https://api.example.com/products/${params.id}`, {
cache: "no-store"
});
if (!res.ok) {
notFound() // Hiển thị 404 page
}
const product = await res.json();
return (
<div className="product-detail">
<div className="product-images">
<img src={product.image} alt={product.name} />
</div>
<div className="product-info">
<h1>{product.name}</h1>
<p className="price">${product.price}</p>
<p className="description">{product.description}</p>
<button className="add-to-cart">Add to Cart</button>
</div>
</div>
);
}
3. SSR trong Pages Router (Legacy)
3.1. getServerSideProps
// pages/news.tsx (Pages Router)
import { GetServerSideProps } from 'next'
interface NewsArticle {
id: string
title: string
content: string
publishedAt: string
}
interface NewsPageProps {
articles: NewsArticle[]
}
export default function NewsPage({ articles }: NewsPageProps) {
return (
<div className="news-page">
<h1>Latest News</h1>
<div className="articles">
{articles.map((article) => (
<article key={article.id}>
<h2>{article.title}</h2>
<p>{article.content}</p>
<time>{article.publishedAt}</time>
</article>
))}
</div>
</div>
);
}
export const getServerSideProps: GetServerSideProps<NewsPageProps> = async (context) => {
// Access request context
const { req, res, query, params } = context
// Fetch data
const response = await fetch('https://api.example.com/news')
const articles = await response.json()
return {
props: {
articles
}
}
}
4. So sánh SSR vs SSG vs ISR
4.1. Comparison Table
| Feature | SSR | SSG | ISR |
|---|---|---|---|
| Build Time | Không có | Có | Có |
| Request Time | Có | Không | Có (revalidate) |
| Data Freshness | Luôn mới | Static | Configurable |
| Performance | Chậm hơn | Nhanh nhất | Nhanh |
| SEO | Tốt | Tốt nhất | Tốt |
| Use Case | Real-time data | Static content | Periodic updates |
4.2. Khi nào dùng SSR?
// ✅ Nên dùng SSR:
// - User-specific data
// - Real-time information
// - Authentication required
// - A/B testing
// ❌ Không nên dùng SSR:
// - Static marketing pages
// - Blog posts (dùng SSG/ISR)
// - Product catalogs (dùng ISR)
5. Performance Optimization
5.1. Cache Headers
// app/api/ssr-cache/route.ts
export async function GET(request: Request) {
const data = await fetchData()
return new Response(JSON.stringify(data), {
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=120',
'CDN-Cache-Control': 'public, max-age=60',
'Vercel-CDN-Cache-Control': 'public, s-maxage=3600',
},
})
}
5.2. Streaming SSR
// app/streaming/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 3000))
return <div>Slow data loaded!</div>
}
export default function StreamingPage() {
return (
<div>
<h1>Streaming SSR Example</h1>
<Suspense fallback={<div>Loading slow component...</div>}>
<SlowComponent />
</Suspense>
</div>
)
}
6. Error Handling trong SSR
6.1. Try-catch và Error Boundaries
// app/ssr-page/page.tsx
import { notFound } from 'next/navigation'
export default async function SSRPage() {
try {
const res = await fetch('https://api.example.com/data', {
cache: 'no-store'
})
if (!res.ok) {
if (res.status === 404) {
notFound() // Show 404 page
}
throw new Error('Failed to fetch data')
}
const data = await res.json()
return (
<div>
<h1>Data loaded successfully</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
} catch (error) {
console.error('SSR Error:', error)
return (
<div className="error-page">
<h1>⚠️ Error loading data</h1>
<p>Please try again later.</p>
</div>
)
}
}
7. Real-world Examples
7.1. E-commerce với Real-time Inventory
// app/products/[id]/page.tsx
export default async function ProductPage({ params }) {
// SSR cho real-time inventory và pricing
const [productRes, inventoryRes, reviewsRes] = await Promise.all([
fetch(`https://api.example.com/products/${params.id}`),
fetch(`https://api.example.com/inventory/${params.id}`, { cache: 'no-store' }),
fetch(`https://api.example.com/reviews/${params.id}`)
])
const [product, inventory, reviews] = await Promise.all([
productRes.json(),
inventoryRes.json(),
reviewsRes.json()
])
return (
<div className="product-page">
<h1>{product.name}</h1>
<p className="price">${inventory.price}</p>
<p className="stock">Stock: {inventory.quantity}</p>
<div className="reviews">
<h2>Reviews ({reviews.length})</h2>
{/* Reviews content */}
</div>
</div>
)
}
7.2. Dashboard với Real-time Analytics
// app/dashboard/analytics/page.tsx
export default async function AnalyticsPage() {
const session = await getServerSession()
if (!session) {
redirect('/login')
}
// Fetch real-time analytics cho user
const analytics = await fetch(`https://api.example.com/analytics/${session.userId}`, {
cache: 'no-store',
headers: {
'Authorization': `Bearer ${session.accessToken}`
}
}).then(res => res.json())
return (
<div className="analytics-dashboard">
<h1>Analytics Dashboard</h1>
<div className="metrics">
<div className="metric">
<h3>Page Views</h3>
<p>{analytics.pageViews}</p>
</div>
<div className="metric">
<h3>Unique Visitors</h3>
<p>{analytics.uniqueVisitors}</p>
</div>
</div>
</div>
)
}
8. Bài tập thực hành
Bài tập 1: Weather App với SSR
Tạo trang /weather với:
- Fetch real-time weather data
- Hiển thị theo location
- Error handling cho API failures
- Loading states
Bài tập 2: User Dashboard
Tạo dashboard với:
- Authentication check
- Fetch user-specific data
- Real-time notifications
- Responsive layout
Bài tập 3: Stock Price Tracker
Tạo stock tracker với:
- Real-time stock prices
- Multiple stock symbols
- Price change indicators
- Auto-refresh functionality
9. Kết luận bài học
- SSR: Render mới cho mỗi request, phù hợp cho real-time data
- Performance: Cần optimize với caching và streaming
- Use Cases: Authentication, personalization, real-time data
- Trade-offs: Server cost vs data freshness
🔑 GHI NHỚ QUAN TRỌNG:
- Dùng SSR cho data thay đổi theo từng request
- Luôn handle errors trong SSR pages
- Consider performance impact với high-traffic pages
- Kết hợp với caching strategies để optimize performance