🖥️ What is Server-Side Rendering?
📋 Definition
Server-Side Rendering (SSR) is a web development technique where HTML pages are generated on the server for each request, then sent to the browser as fully-formed HTML documents with content already populated.
🔄 SSR Process Flow
Browser Request
User navigates to URL
Server Processing
Server fetches data & renders HTML
Full HTML Response
Complete page sent to browser
Hydration
JavaScript makes page interactive
✨ Key Characteristics
Fast First Paint
Content visible immediately as HTML is pre-rendered
SEO Friendly
Search engines can easily crawl and index content
Universal Apps
Same code runs on server and client
Fresh Data
Content generated with latest data on each request
⚖️ SSR vs CSR: The Complete Picture
📊 Loading Timeline Comparison
🖥️ Server-Side Rendering
💻 Client-Side Rendering
📈 Core Web Vitals Comparison
First Contentful Paint (FCP)
Largest Contentful Paint (LCP)
Time to Interactive (TTI)
⚖️ Trade-offs Analysis
🚀 Performance
SSR Wins
- Faster First Contentful Paint
- Better Largest Contentful Paint
- Immediate content visibility
CSR Wins
- Faster subsequent navigation
- No server processing delay
- Better caching strategies
💰 Infrastructure
SSR Costs
- Server compute resources
- Higher hosting costs
- Complex deployment
CSR Benefits
- Static hosting (CDN)
- Lower server costs
- Simple deployment
💧 Hydration: Making Static HTML Interactive
🔍 What is Hydration?
Hydration is the process where JavaScript "brings to life" the static HTML sent from the server by attaching event listeners, initializing component state, and making the page interactive.
🔄 The Hydration Process
1. Static HTML Arrives
2. JavaScript Downloads & Executes
// React hydration
ReactDOM.hydrate(
<App />,
document.getElementById('root')
);
// Attaching event listeners
button.addEventListener('click', handleClick);
3. Fully Interactive
⚠️ Hydration Challenges
Hydration Delay
Page appears interactive but doesn't respond to clicks until hydration completes
Hydration Mismatch
Server and client render different content, causing React to re-render
Bundle Size Impact
Large JavaScript bundles delay hydration and interactivity
🚀 Hydration Optimization Techniques
📦 Selective Hydration
// Only hydrate interactive components
const InteractiveButton = lazy(() =>
import('./InteractiveButton')
);
function Page() {
return (
<div>
<h1>Static Content</h1>
<Suspense fallback={<div>Loading...</div>}>
<InteractiveButton />
</Suspense>
</div>
);
}
⚡ Progressive Hydration
// Hydrate components as they become visible
const useIntersectionObserver = (ref) => {
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
setIsVisible(entry.isIntersecting);
});
if (ref.current) observer.observe(ref.current);
return () => observer.disconnect();
}, []);
return isVisible;
};
🎮 Next.js SSR Live Demo
📱 Next.js Server-Side Rendering Example
This demo simulates how Next.js handles SSR with getServerSideProps, showing the complete request lifecycle.
1. Browser Request
2. Server Processing
export async function getServerSideProps(context) {
const { params } = context;
const product = await fetchProduct(params.id);
return {
props: { product }
};
}
3. HTML Generation
4. Browser Receives & Hydrates
📊 Request Metrics
💻 Next.js SSR Code Examples
pages/products/[id].js
import { GetServerSideProps } from 'next';
interface Product {
id: string;
name: string;
price: number;
description: string;
}
interface Props {
product: Product;
}
export default function ProductPage({ product }: Props) {
return (
<div>
<h1>{product.name}</h1>
<p>Price: ${product.price}</p>
<p>{product.description}</p>
</div>
);
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const { params } = context;
// This runs on the server for every request
const product = await fetch(`${process.env.API_URL}/products/${params.id}`)
.then(res => res.json());
if (!product) {
return {
notFound: true,
};
}
return {
props: {
product,
},
};
};
pages/api/products/[id].js
import { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { id } = req.query;
if (req.method === 'GET') {
try {
// Simulate database fetch
const product = await db.products.findUnique({
where: { id: String(id) }
});
if (!product) {
return res.status(404).json({ error: 'Product not found' });
}
// Set cache headers for better performance
res.setHeader('Cache-Control', 'public, s-maxage=60, stale-while-revalidate=300');
return res.status(200).json(product);
} catch (error) {
return res.status(500).json({ error: 'Internal server error' });
}
}
return res.status(405).json({ error: 'Method not allowed' });
}
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
// Enable experimental features
experimental: {
// Enable React 18 features
reactRoot: true,
// Enable Concurrent Features
concurrentFeatures: true,
},
// Configure image optimization
images: {
domains: ['example.com'],
formats: ['image/webp', 'image/avif'],
},
// Configure headers for better performance
async headers() {
return [
{
source: '/api/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=60, s-maxage=60',
},
],
},
];
},
// Configure redirects
async redirects() {
return [
{
source: '/old-products/:id',
destination: '/products/:id',
permanent: true,
},
];
},
// Enable compression
compress: true,
// Configure build output
output: 'standalone', // For Docker deployments
};
module.exports = nextConfig;
⚡ SSR Performance Optimization
🎯 Performance Bottlenecks in SSR
Slow Server Processing
Data fetching and HTML generation on every request
API Dependencies
Server must wait for external API calls to complete
Database Queries
Complex queries and N+1 problems slow down rendering
Heavy Hydration
Large JavaScript bundles delay interactivity
🚀 Optimization Strategies
💾 Caching Strategies
Page-Level Caching
// Cache entire page for 60 seconds
export const getServerSideProps = async (context) => {
context.res.setHeader(
'Cache-Control',
'public, s-maxage=60, stale-while-revalidate=300'
);
const data = await fetchData();
return { props: { data } };
};
Data-Level Caching
// Cache API responses with Redis
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
export const getServerSideProps = async () => {
const cacheKey = 'products:featured';
let products = await redis.get(cacheKey);
if (!products) {
products = await fetchFeaturedProducts();
await redis.setex(cacheKey, 300, JSON.stringify(products));
} else {
products = JSON.parse(products);
}
return { props: { products } };
};
📡 Data Fetching Optimization
Parallel Data Fetching
export const getServerSideProps = async () => {
// Fetch data in parallel instead of sequentially
const [user, products, reviews] = await Promise.all([
fetchUser(),
fetchProducts(),
fetchReviews()
]);
return {
props: { user, products, reviews }
};
};
GraphQL with DataLoader
// Batch and cache database queries
const DataLoader = require('dataloader');
const userLoader = new DataLoader(async (userIds) => {
const users = await db.users.findMany({
where: { id: { in: userIds } }
});
return userIds.map(id => users.find(user => user.id === id));
});
// Usage in resolver
const user = await userLoader.load(userId);
🎮 Performance Optimization Demo
Server Processing Time
Time to First Byte
First Contentful Paint
Total Page Load
📊 Optimization Impact
🎯 SSR Summary & Best Practices
📋 Key Takeaways
✅ SSR Strengths
- Fast First Contentful Paint
- Excellent SEO and social sharing
- Better Core Web Vitals scores
- Works without JavaScript
- Fresh data on every request
❌ SSR Challenges
- Higher server costs and complexity
- Slower Time to Interactive
- Hydration mismatches and delays
- More complex deployment
- API dependency bottlenecks
🏆 SSR Best Practices
🚀 Performance
Implement Smart Caching
Use page-level, data-level, and CDN caching strategically
Optimize Data Fetching
Fetch data in parallel, use GraphQL, implement connection pooling
Minimize Hydration Time
Code split, selective hydration, progressive enhancement
🛠️ Development
Handle Hydration Mismatches
Use suppressHydrationWarning sparingly, ensure server-client consistency
Implement Error Boundaries
Graceful error handling for server and client-side errors
Monitor Performance
Track TTFB, hydration time, and Core Web Vitals
🎯 When to Choose SSR
✅ Perfect for:
- E-commerce product pages
- News and content websites
- Marketing and landing pages
- Social media platforms
- SEO-critical applications
❌ Consider alternatives for:
- Admin dashboards
- Internal tools
- Real-time applications
- Apps with frequent updates
- Cost-sensitive projects
🚀 Next: Explore Static Site Generation
Learn how SSG combines the best of SSR and CSR while eliminating server costs.
Continue to SSG →