React & Next.js Topic
Next.js Rendering Strategies
Overview of Rendering Strategies
| Strategy | Data Fetching | When HTML is Generated | SEO | Performance | Use Cases |
|---|---|---|---|---|---|
| SSR | Server on request | Each request | Excellent | Good (slower initial) | Dashboard, E-commerce, News |
| SSG | Build time | Build time | Excellent | Excellent (fastest) | Blog, Documentation, Marketing |
| CSR | Client | Client | Poor | Good (slower FCP) | Web Apps, Tools, Dashboards |
| ISR | Build + Runtime | Hybrid | Excellent | Excellent | E-commerce, News, Content |
1. SSR (Server-Side Rendering)
What is SSR?
-
HTML is generated on the server for each request
-
Data is fetched on the server before sending to client
-
Full HTML is sent to browser, then hydrated on client
When to Use SSR:
-
Dynamic data that changes frequently
-
User-specific content (dashboards, user profiles)
-
SEO-critical pages with dynamic content
-
Real-time data that can't be cached
-
Authentication-required pages
Next.js SSR Example:
jsx
// pages/dashboard.js - Using getServerSideProps
export default function Dashboard({ user, recentActivity, serverTime }) {
return (
<div>
<h1>Welcome, {user.name}!</h1>
<p>Server rendered at: {serverTime}</p>
<div className="recent-activity">
<h2>Recent Activity</h2>
{recentActivity.map(activity => (
<div key={activity.id} className="activity-item">
<span>{activity.action}</span>
<time>{activity.timestamp}</time>
</div>
))}
</div>
</div>
);
}
// This runs on EVERY request
export async function getServerSideProps(context) {
const { req, res, query, params } = context;
// Access request data (cookies, headers, etc.)
const token = req.cookies.authToken;
if (!token) {
return {
redirect: {
destination: '/login',
permanent: false,
},
};
}
try {
// Fetch user-specific data
const [userResponse, activityResponse] = await Promise.all([
fetch(`https://api.example.com/user`, {
headers: { Authorization: `Bearer ${token}` }
}),
fetch(`https://api.example.com/activity/recent`, {
headers: { Authorization: `Bearer ${token}` }
})
]);
const user = await userResponse.json();
const recentActivity = await activityResponse.json();
// Data is fresh for every request
return {
props: {
user,
recentActivity,
serverTime: new Date().toISOString()
},
};
} catch (error) {
// Handle errors gracefully
return {
redirect: {
destination: '/error',
permanent: false,
},
};
}
}
React-Only SSR Example (without Next.js):
jsx
// server/index.js (Express.js)
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import App from '../src/App';
const app = express();
app.get('*', async (req, res) => {
try {
// Fetch data on server
const userData = await fetchUserData(req.cookies.userId);
const posts = await fetchRecentPosts();
// Render React to HTML string
const html = renderToString(
<App userData={userData} posts={posts} />
);
// Send complete HTML response
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>SSR Example</title>
</head>
<body>
<div id="root">${html}</div>
<script>
// Pass data to client for hydration
window.__INITIAL_DATA__ = ${JSON.stringify({ userData, posts })};
</script>
<script src="/client-bundle.js"></script>
</body>
</html>
`);
} catch (error) {
res.status(500).send('Server Error');
}
});
2. SSG (Static Site Generation)
What is SSG?
-
HTML is generated at build time
-
Same HTML is served for all users
-
Can be deployed to CDN for maximum performance
-
Data is fetched during build process
When to Use SSG:
-
Content that doesn't change often (blog posts, documentation)
-
Marketing pages (homepage, about, contact)
-
Product catalogs that update infrequently
-
SEO-focused content that needs fast loading
-
High-traffic pages where performance is critical
Next.js SSG Example:
jsx
// pages/blog/[slug].js - Static Generation with dynamic routes
export default function BlogPost({ post, relatedPosts }) {
if (!post) {
return <div>Post not found</div>;
}
return (
<article>
<header>
<h1>{post.title}</h1>
<p>By {post.author} • {new Date(post.publishedAt).toLocaleDateString()}</p>
</header>
<div className="content" dangerouslySetInnerHTML={{ __html: post.content }} />
<aside className="related-posts">
<h3>Related Posts</h3>
{relatedPosts.map(related => (
<div key={related.id}>
<a href={`/blog/${related.slug}`}>{related.title}</a>
</div>
))}
</aside>
</article>
);
}
// Define which paths to pre-render at build time
export async function getStaticPaths() {
// Fetch all blog post slugs
const posts = await fetch('https://api.example.com/posts').then(res => res.json());
const paths = posts.map(post => ({
params: { slug: post.slug }
}));
return {
paths,
fallback: false // Show 404 for paths not generated at build time
// fallback: true - Generate on demand
// fallback: 'blocking' - Generate on demand, wait for generation
};
}
// Fetch data at build time
export async function getStaticProps({ params }) {
try {
const [postResponse, relatedResponse] = await Promise.all([
fetch(`https://api.example.com/posts/${params.slug}`),
fetch(`https://api.example.com/posts/${params.slug}/related`)
]);
const post = await postResponse.json();
const relatedPosts = await relatedResponse.json();
if (!post) {
return {
notFound: true // Show 404 page
};
}
return {
props: {
post,
relatedPosts
},
// Re-generate page in background after 1 hour
revalidate: 3600 // ISR: Incremental Static Regeneration
};
} catch (error) {
return {
notFound: true
};
}
}
Static Generation for Multiple Pages:
jsx
// pages/index.js - Homepage
export default function Home({ featuredPosts, stats }) {
return (
<div>
<h1>Welcome to Our Blog</h1>
<div className="stats">
<span>{stats.totalPosts} Posts</span>
<span>{stats.totalAuthors} Authors</span>
</div>
<section className="featured-posts">
<h2>Featured Posts</h2>
{featuredPosts.map(post => (
<article key={post.id}>
<h3>{post.title}</h3>
<p>{post.excerpt}</p>
<a href={`/blog/${post.slug}`}>Read More</a>
</article>
))}
</section>
</div>
);
}
// This runs only at build time
export async function getStaticProps() {
const [featuredResponse, statsResponse] = await Promise.all([
fetch('https://api.example.com/posts/featured'),
fetch('https://api.example.com/stats')
]);
const featuredPosts = await featuredResponse.json();
const stats = await statsResponse.json();
return {
props: {
featuredPosts,
stats
},
revalidate: 3600 // Revalidate every hour
};
}
3. CSR (Client-Side Rendering)
What is CSR?
-
HTML is generated in the browser using JavaScript
-
Initial page load is minimal, then content is populated
-
Data is fetched on the client after component mounts
-
Better for interactive applications
When to Use CSR:
-
Highly interactive applications (dashboards, admin panels)
-
User-specific content that doesn't need SEO
-
Real-time applications (chat, notifications)
-
When you need rich client-side interactions
-
Pages behind authentication
React CSR Example:
jsx
import { useState, useEffect } from 'react';
import useSWR from 'swr';
// Custom hook for data fetching
function useUserProfile(userId) {
const { data, error, isLoading, mutate } = useSWR(
userId ? `/api/users/${userId}` : null,
url => fetch(url).then(res => res.json()),
{
revalidateOnFocus: false,
revalidateOnReconnect: true
}
);
return {
user: data,
isLoading,
error,
refresh: mutate
};
}
function UserDashboard() {
const [userId, setUserId] = useState(null);
const [activeTab, setActiveTab] = useState('profile');
const { user, isLoading, error, refresh } = useUserProfile(userId);
// Simulate user authentication
useEffect(() => {
const storedUserId = localStorage.getItem('userId');
setUserId(storedUserId);
}, []);
if (!userId) {
return <div>Please log in to view your dashboard.</div>;
}
if (error) {
return (
<div>
<p>Error loading profile: {error.message}</p>
<button onClick={refresh}>Retry</button>
</div>
);
}
if (isLoading) {
return (
<div className="loading">
<div className="spinner"></div>
<p>Loading your dashboard...</p>
</div>
);
}
return (
<div className="dashboard">
<header>
<h1>Welcome back, {user?.name}!</h1>
<button onClick={refresh}>Refresh Data</button>
</header>
<nav className="tabs">
<button
className={activeTab === 'profile' ? 'active' : ''}
onClick={() => setActiveTab('profile')}
>
Profile
</button>
<button
className={activeTab === 'settings' ? 'active' : ''}
onClick={() => setActiveTab('settings')}
>
Settings
</button>
<button
className={activeTab === 'analytics' ? 'active' : ''}
onClick={() => setActiveTab('analytics')}
>
Analytics
</button>
</nav>
<div className="tab-content">
{activeTab === 'profile' && <UserProfile user={user} />}
{activeTab === 'settings' && <UserSettings user={user} />}
{activeTab === 'analytics' && <UserAnalytics userId={userId} />}
</div>
</div>
);
}
// Child components that fetch their own data
function UserAnalytics({ userId }) {
const [timeRange, setTimeRange] = useState('7d');
const { data: analytics, isLoading } = useSWR(
`/api/analytics/${userId}?range=${timeRange}`,
url => fetch(url).then(res => res.json())
);
if (isLoading) return <div>Loading analytics...</div>;
return (
<div>
<select value={timeRange} onChange={(e) => setTimeRange(e.target.value)}>
<option value="24h">24 Hours</option>
<option value="7d">7 Days</option>
<option value="30d">30 Days</option>
</select>
<div className="analytics-data">
<div>Page Views: {analytics?.pageViews}</div>
<div>Visitors: {analytics?.uniqueVisitors}</div>
<div>Bounce Rate: {analytics?.bounceRate}%</div>
</div>
</div>
);
}
Next.js Hybrid Approach (SSG + CSR):
jsx
// pages/products/index.js - SSG for initial render, CSR for interactions
export default function ProductsPage({ initialProducts, categories }) {
const [selectedCategory, setSelectedCategory] = useState('all');
const [sortBy, setSortBy] = useState('name');
const [products, setProducts] = useState(initialProducts);
const [loading, setLoading] = useState(false);
// Client-side filtering and sorting
useEffect(() => {
if (selectedCategory === 'all' && sortBy === 'name') {
setProducts(initialProducts);
return;
}
setLoading(true);
// Simulate API call for filtered/sorted data
const fetchFilteredProducts = async () => {
const query = new URLSearchParams({
category: selectedCategory,
sort: sortBy
});
const response = await fetch(`/api/products?${query}`);
const filteredProducts = await response.json();
setProducts(filteredProducts);
setLoading(false);
};
fetchFilteredProducts();
}, [selectedCategory, sortBy, initialProducts]);
return (
<div>
<h1>Our Products</h1>
{/* Filters - client side interaction */}
<div className="filters">
<select
value={selectedCategory}
onChange={(e) => setSelectedCategory(e.target.value)}
>
<option value="all">All Categories</option>
{categories.map(category => (
<option key={category.id} value={category.slug}>
{category.name}
</option>
))}
</select>
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
>
<option value="name">Name</option>
<option value="price">Price</option>
<option value="rating">Rating</option>
</select>
</div>
{/* Products list */}
{loading ? (
<div>Loading products...</div>
) : (
<div className="products-grid">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
)}
</div>
);
}
// Static generation for initial data
export async function getStaticProps() {
const [productsResponse, categoriesResponse] = await Promise.all([
fetch('https://api.example.com/products'),
fetch('https://api.example.com/categories')
]);
const initialProducts = await productsResponse.json();
const categories = await categoriesResponse.json();
return {
props: {
initialProducts,
categories
},
revalidate: 3600 // Revalidate every hour
};
}
4. When to Use Which Strategy - Decision Guide
Decision Flowchart:
text
Is the content dynamic per user?
├── Yes → Does it need SEO?
│ ├── Yes → Use SSR (getServerSideProps)
│ └── No → Use CSR
└── No → Does content change frequently?
├── Yes → Use ISR (revalidate)
└── No → Use SSG (getStaticProps)
Detailed Use Case Analysis:
jsx
// USE CASE 1: E-commerce Product Page
// Mix of SSG for product data + CSR for user interactions
export default function ProductPage({ product }) {
// SSG: Product details, reviews, specifications
// CSR: Add to cart, wishlist, real-time stock, recommendations
return (
<div>
{/* SSG Content */}
<h1>{product.name}</h1>
<p>{product.description}</p>
{/* CSR Interactions */}
<AddToCart productId={product.id} />
<WishlistButton productId={product.id} />
<RealTimeStock productId={product.id} />
</div>
);
}
// USE CASE 2: Blog Platform
// SSG for posts + ISR for updates
export default function BlogPost({ post }) {
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
{/* Static comments loaded via CSR */}
<CommentsSection postId={post.id} />
</article>
);
}
// USE CASE 3: User Dashboard
// SSR for initial data + CSR for real-time updates
export default function Dashboard({ initialData }) {
const [realTimeData, setRealTimeData] = useState(initialData);
useEffect(() => {
// WebSocket or polling for real-time updates
const ws = new WebSocket('/api/realtime');
ws.onmessage = (event) => {
setRealTimeData(JSON.parse(event.data));
};
return () => ws.close();
}, []);
return (
<div>
<h1>Dashboard</h1>
<RealTimeMetrics data={realTimeData} />
</div>
);
}
Performance Comparison:
jsx
// Performance metrics for each strategy
const performanceComparison = {
SSR: {
timeToFirstByte: 'Medium',
firstContentfulPaint: 'Fast',
timeToInteractive: 'Medium',
seo: 'Excellent',
cache: 'Poor'
},
SSG: {
timeToFirstByte: 'Very Fast',
firstContentfulPaint: 'Very Fast',
timeToInteractive: 'Fast',
seo: 'Excellent',
cache: 'Excellent'
},
CSR: {
timeToFirstByte: 'Fast',
firstContentfulPaint: 'Slow',
timeToInteractive: 'Medium',
seo: 'Poor',
cache: 'Good'
},
ISR: {
timeToFirstByte: 'Very Fast',
firstContentfulPaint: 'Very Fast',
timeToInteractive: 'Fast',
seo: 'Excellent',
cache: 'Excellent'
}
};
Summary:
-
Use SSG whenever possible for maximum performance
-
Use SSR for user-specific, SEO-critical dynamic content
-
Use CSR for highly interactive, private content
-
Combine strategies for optimal results
-
Implement ISR for content that updates periodically
-
Monitor real user metrics to validate your choices
This comprehensive guide helps you choose the right rendering strategy based on your specific use case, balancing performance, SEO, and user experience requirements.
Comments (0)
No comments yet. Be the first to comment!