Comparison Table
Strategy |
Build Time |
Request Time |
Data Freshness |
Performance |
Use Case |
SSG |
✅ Pre-rendered |
✅ Instant |
❌ Static |
🟢 Best |
Blog, docs, marketing |
ISR |
✅ Pre-rendered |
✅ Instant |
🟡 Periodic |
🟢 Best |
E-commerce, news |
SSR |
❌ Not built |
⏱️ Server time |
✅ Fresh |
🟡 Good |
Dashboard, user content |
CSR |
❌ Not built |
⏱️ Client time |
✅ Fresh |
🟡 Good |
Admin panels, SPAs |
Basic SSG
export default async function BlogPost({ params }) {
const post = await getPost(params.slug);
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({
slug: post.slug,
}));
}
SSG with Data Fetching
export default async function ProductsPage() {
const products = await getProducts();
return (
<div>
<h1>Products</h1>
{products.map(product => (
<div key={product.id}>
<h2>{product.name}</h2>
<p>{product.description}</p>
</div>
))}
</div>
);
}
export async function generateStaticParams() {
const products = await getProducts();
return products.map((product) => ({
id: product.id,
}));
}
SSG with External Data
export default async function NewsPage() {
const news = await fetch('https://api.example.com/news', {
next: { revalidate: 3600 }
}).then(res => res.json());
return (
<div>
{news.map(article => (
<article key={article.id}>
<h2>{article.title}</h2>
<p>{article.summary}</p>
</article>
))}
</div>
);
}
Time-Based Revalidation
export default async function ProductPage({ params }) {
const product = await getProduct(params.id);
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>Price: ${product.price}</p>
</div>
);
}
export const revalidate = 60;
export async function generateStaticParams() {
const products = await getProducts();
return products.map((product) => ({
id: product.id,
}));
}
On-Demand Revalidation
import { revalidatePath, revalidateTag } from 'next/cache';
export async function POST(request) {
const { path, tag } = await request.json();
if (path) {
revalidatePath(path);
}
if (tag) {
revalidateTag(tag);
}
return Response.json({ revalidated: true });
}
export default async function ProductPage({ params }) {
const product = await fetch(`/api/products/${params.id}`, {
next: { tags: ['product', `product-${params.id}`] }
}).then(res => res.json());
return <div>{/* product content */}</div>;
}
await fetch('/api/revalidate', {
method: 'POST',
body: JSON.stringify({
path: `/products/${productId}`,
tag: `product-${productId}`
})
});
Conditional ISR
export default async function BlogPost({ params }) {
const post = await getPost(params.slug);
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({
slug: post.slug,
}));
}
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
if (post.type === 'news') {
return { revalidate: 300 };
} else if (post.type === 'tutorial') {
return { revalidate: 86400 };
}
return { revalidate: 3600 };
}
Basic SSR
export default async function DashboardPage() {
const user = await getCurrentUser();
const data = await fetchUserData(user.id);
return (
<div>
<h1>Welcome, {user.name}</h1>
<div>{/* dashboard content */}</div>
</div>
);
}
SSR with Dynamic Data
export default async function SearchPage({ searchParams }) {
const query = searchParams.q || '';
const results = await searchProducts(query);
return (
<div>
<h1>Search Results</h1>
{results.map(result => (
<div key={result.id}>
<h2>{result.name}</h2>
<p>{result.description}</p>
</div>
))}
</div>
);
}
SSR with Authentication
import { redirect } from 'next/navigation';
export default async function ProfilePage() {
const user = await getCurrentUser();
if (!user) {
redirect('/login');
}
const profile = await getUserProfile(user.id);
return (
<div>
<h1>Profile</h1>
<p>Name: {profile.name}</p>
<p>Email: {profile.email}</p>
</div>
);
}
Basic CSR
'use client';
import { useState, useEffect } from 'react';
export default function ClientComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
const response = await fetch('/api/data');
const result = await response.json();
setData(result);
setLoading(false);
}
fetchData();
}, []);
if (loading) return <div>Loading...</div>;
return (
<div>
<h1>Client-Side Data</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
CSR with SWR
'use client';
import useSWR from 'swr';
const fetcher = (url) => fetch(url).then(res => res.json());
export default function SWRComponent() {
const { data, error, isLoading } = useSWR('/api/data', fetcher, {
refreshInterval: 5000,
revalidateOnFocus: true
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h1>Real-time Data</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
Hybrid Approach
import ClientComponent from './ClientComponent';
export default async function HybridPage() {
const staticData = await getStaticData();
return (
<div>
<h1>Hybrid Page</h1>
{/* Server-rendered content */}
<div>
<h2>Static Content</h2>
<p>{staticData.description}</p>
</div>
{/* Client-side content */}
<ClientComponent />
</div>
);
}
Streaming with Suspense
import { Suspense } from 'react';
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 2000));
const data = await fetch('https://api.example.com/slow-data');
return data.json();
}
export default function StreamingPage() {
return (
<div>
<h1>Streaming Page</h1>
{/* Fast content renders immediately */}
<div>
<h2>Fast Content</h2>
<p>This content loads immediately.</p>
</div>
{/* Slow content streams in */}
<Suspense fallback={<div>Loading slow content...</div>}>
<SlowComponent />
</Suspense>
</div>
);
}
Parallel Data Fetching
import { Suspense } from 'react';
async function UserProfile({ userId }) {
const user = await fetch(`/api/users/${userId}`);
return user.json();
}
async function UserPosts({ userId }) {
const posts = await fetch(`/api/users/${userId}/posts`);
return posts.json();
}
async function UserStats({ userId }) {
const stats = await fetch(`/api/users/${userId}/stats`);
return stats.json();
}
export default function DashboardPage({ params }) {
return (
<div>
<h1>Dashboard</h1>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '1rem' }}>
<Suspense fallback={<div>Loading profile...</div>}>
<UserProfile userId={params.userId} />
</Suspense>
<Suspense fallback={<div>Loading posts...</div>}>
<UserPosts userId={params.userId} />
</Suspense>
<Suspense fallback={<div>Loading stats...</div>}>
<UserStats userId={params.userId} />
</Suspense>
</div>
</div>
);
}
Page-Level Configuration
export const dynamic = 'force-static';
export const revalidate = 3600;
export const fetchCache = 'force-cache';
export const runtime = 'nodejs';
export default function BlogPost({ params }) {
}
Layout-Level Configuration
export const dynamic = 'force-dynamic';
export const revalidate = 0;
export default function DashboardLayout({ children }) {
return (
<div>
<nav>Dashboard Navigation</nav>
{children}
</div>
);
}
Bundle Analysis
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({
});
Code Splitting
import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('./HeavyChart'), {
ssr: false,
loading: () => <div>Loading chart...</div>
});
const MapComponent = dynamic(() => import('./MapComponent'), {
ssr: false,
loading: () => <div>Loading map...</div>
});
export default function Page() {
return (
<div>
<h1>Page with Heavy Components</h1>
<HeavyChart />
<MapComponent />
</div>
);
}
Preloading Strategies
'use client';
import { useEffect } from 'react';
export default function PreloadComponent() {
useEffect(() => {
const link = document.createElement('link');
link.rel = 'preload';
link.href = '/api/critical-data';
link.as = 'fetch';
document.head.appendChild(link);
}, []);
return <div>Component with preloading</div>;
}
Choosing the Right Strategy
Error Handling
'use client';
export default function Error({ error, reset }) {
return (
<div>
<h2>Something went wrong!</h2>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
);
}
export default function GlobalError({ error, reset }) {
return (
<html>
<body>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</body>
</html>
);
}
Loading States
export default function Loading() {
return (
<div className="loading">
<div className="spinner"></div>
<p>Loading...</p>
</div>
);
}
export default function NotFound() {
return (
<div>
<h2>Page Not Found</h2>
<p>Could not find the requested resource.</p>
</div>
);
}
Performance Monitoring
export function measurePageLoad() {
if (typeof window !== 'undefined') {
const navigation = performance.getEntriesByType('navigation')[0];
return {
dns: navigation.domainLookupEnd - navigation.domainLookupStart,
tcp: navigation.connectEnd - navigation.connectStart,
ttfb: navigation.responseStart - navigation.requestStart,
domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
load: navigation.loadEventEnd - navigation.loadEventStart,
};
}
}
'use client';
import { useEffect } from 'react';
import { measurePageLoad } from '@/lib/performance';
export default function PerformanceMonitor() {
useEffect(() => {
const metrics = measurePageLoad();
console.log('Page Load Metrics:', metrics);
fetch('/api/analytics/performance', {
method: 'POST',
body: JSON.stringify(metrics)
});
}, []);
return null;
}