Next.js Server-side Rendering
Introduction
Server-side Rendering (SSR) is one of Next.js's most powerful features. Unlike traditional React applications where rendering happens entirely in the browser (client-side), SSR executes the rendering process on the server and sends fully rendered HTML to the client. This approach offers significant advantages for performance and search engine optimization (SEO).
In this guide, you'll learn:
- What Server-side Rendering is and how it works
- How to implement SSR in Next.js
- When to use SSR versus other rendering methods
- Best practices and performance considerations
Understanding Server-side Rendering
What is Server-side Rendering?
Server-side Rendering is a technique where a web page is rendered on the server and sent to the client as fully formed HTML. This differs from client-side rendering, where the browser downloads minimal HTML and JavaScript, then builds the page client-side.
How SSR Works in Next.js
When a user requests a page in a Next.js application with SSR:
- The server receives the request
- Next.js renders the React components on the server
- The server fetches any necessary data
- Next.js generates HTML with the data already included
- The HTML is sent to the client along with minimal JavaScript
- The client's browser displays the HTML immediately
- The JavaScript loads and "hydrates" the page, making it interactive
This process gives users a fast initial page load and ensures content is visible to search engines.
Implementing Server-side Rendering in Next.js
Basic SSR with getServerSideProps
The primary way to implement SSR in Next.js is by using the getServerSideProps
function in your page components.
Here's a basic example:
// pages/products.js
function ProductsPage({ products }) {
return (
<div>
<h1>Products</h1>
<ul>
{products.map((product) => (
<li key={product.id}>{product.name} - ${product.price}</li>
))}
</ul>
</div>
);
}
// This function runs on the server for every request
export async function getServerSideProps() {
// Fetch data from an API
const res = await fetch('https://api.example.com/products');
const products = await res.json();
// Pass the data to the page component as props
return {
props: {
products,
},
};
}
export default ProductsPage;
When a user visits /products
, Next.js will:
- Execute
getServerSideProps
on the server - Wait for the data fetching to complete
- Render the page with the fetched data
- Send the fully rendered HTML to the client
Accessing Request and Response Objects
With getServerSideProps
, you can access the request and response objects, which is useful for handling cookies, headers, or implementing authentication:
// pages/dashboard.js
export async function getServerSideProps({ req, res }) {
// Check for authentication cookie
const authCookie = req.cookies.authToken;
if (!authCookie) {
// If not authenticated, redirect to login
return {
redirect: {
destination: '/login',
permanent: false,
},
};
}
// Fetch user data using the auth token
const userData = await fetchUserData(authCookie);
return {
props: { user: userData },
};
}
function Dashboard({ user }) {
return (
<div>
<h1>Welcome, {user.name}!</h1>
{/* Dashboard content */}
</div>
);
}
export default Dashboard;
Dynamic Routing with SSR
You can combine SSR with dynamic routes to create dynamic pages that fetch data based on URL parameters:
// pages/posts/[id].js
function Post({ post }) {
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
export async function getServerSideProps({ params }) {
// The params object contains the route parameters
const { id } = params;
// Fetch data for a specific post
const res = await fetch(`https://api.example.com/posts/${id}`);
const post = await res.json();
return {
props: { post },
};
}
export default Post;
When someone visits /posts/123
, Next.js will render the page on the server with data for post ID 123.
When to Use Server-side Rendering
SSR is particularly beneficial in certain scenarios:
1. SEO-Critical Pages
Pages that need to be indexed by search engines should use SSR to ensure content is fully crawlable.
Good candidates for SSR:
- Blog posts
- Product pages
- Landing pages
- Marketing content
2. Dynamic, Personalized Content
Content that changes frequently or is personalized for each visitor:
- Dashboards with user-specific data
- Pages with frequently changing data
- Content based on user location or preferences
3. Pages That Need Good Social Sharing
Pages that might be shared on social media platforms benefit from SSR because the platforms can properly display preview cards with metadata.
Real-World Example: E-commerce Product Page
Let's create an e-commerce product detail page with SSR, showing how to incorporate data fetching, SEO optimization, and user personalization:
// pages/products/[productId].js
import Head from 'next/head';
import { useState } from 'react';
function ProductDetail({ product, relatedProducts, userCountry }) {
const [quantity, setQuantity] = useState(1);
// Format price based on user's country
const price = new Intl.NumberFormat(
userCountry === 'US' ? 'en-US' : 'en-GB',
{ style: 'currency', currency: userCountry === 'US' ? 'USD' : 'GBP' }
).format(product.price);
return (
<>
<Head>
<title>{product.name} | MyStore</title>
<meta name="description" content={product.shortDescription} />
<meta property="og:title" content={product.name} />
<meta property="og:description" content={product.shortDescription} />
<meta property="og:image" content={product.image} />
</Head>
<div className="product-container">
<div className="product-image">
<img src={product.image} alt={product.name} />
</div>
<div className="product-details">
<h1>{product.name}</h1>
<p className="price">{price}</p>
<div className="rating">
{/* Rating component */}
{Array(5).fill().map((_, i) => (
<span key={i}>
{i < product.rating ? "★" : "☆"}
</span>
))}
<span>({product.reviewCount} reviews)</span>
</div>
<p className="description">{product.description}</p>
<div className="purchase-options">
<div className="quantity">
<button onClick={() => quantity > 1 && setQuantity(quantity - 1)}>-</button>
<span>{quantity}</span>
<button onClick={() => setQuantity(quantity + 1)}>+</button>
</div>
<button className="add-to-cart">Add to Cart</button>
</div>
</div>
</div>
<div className="related-products">
<h2>You might also like</h2>
<div className="product-grid">
{relatedProducts.map(relatedProduct => (
<div key={relatedProduct.id} className="product-card">
<img src={relatedProduct.image} alt={relatedProduct.name} />
<h3>{relatedProduct.name}</h3>
<p>{new Intl.NumberFormat(
userCountry === 'US' ? 'en-US' : 'en-GB',
{ style: 'currency', currency: userCountry === 'US' ? 'USD' : 'GBP' }
).format(relatedProduct.price)}</p>
</div>
))}
</div>
</div>
</>
);
}
export async function getServerSideProps({ params, req }) {
const { productId } = params;
// Get user's country from request headers or IP address
const userCountry = req.headers['x-country'] || 'US';
// Fetch the product details
const productRes = await fetch(`https://api.example.com/products/${productId}`);
const product = await productRes.json();
// Fetch related products
const relatedRes = await fetch(
`https://api.example.com/products/related?category=${product.category}&limit=4`
);
const relatedProducts = await relatedRes.json();
// Check if product exists
if (!product) {
return {
notFound: true, // This will return a 404 page
};
}
return {
props: {
product,
relatedProducts,
userCountry,
},
};
}
export default ProductDetail;
This example demonstrates:
- Dynamic routing with the
[productId]
parameter - SEO optimization with proper meta tags
- Personalization based on the user's country
- Complex UI with interactive elements
- Error handling with the
notFound
property
Server-side Rendering vs. Other Rendering Methods
Next.js offers multiple rendering methods, each with its own use cases:
Rendering Method | When to Use | Pros | Cons |
---|---|---|---|
Server-side Rendering (SSR) | Dynamic, personalized content; SEO-critical pages | - Fresh data on each request - Good for SEO - Personalized content | - Slower than static pages - Higher server load |
Static Site Generation (SSG) | Content that rarely changes | - Extremely fast - Minimal server load - Can be served from CDN | - Not suitable for dynamic content - Requires rebuild for updates |
Incremental Static Regeneration (ISR) | Frequently updated content with good performance needs | - Fast like SSG - Updates at intervals - Low server load | - Data can be stale between regenerations |
Client-side Rendering | Dashboard, app-like interfaces | - Interactive without full page loads - Reduced bandwidth after initial load | - Poor SEO - Slower initial load |
Performance Optimization Tips for SSR
1. Use Caching When Possible
Next.js allows you to add cache headers to SSR responses:
export async function getServerSideProps({ req, res }) {
// Cache this page for up to 10 seconds
res.setHeader('Cache-Control', 's-maxage=10, stale-while-revalidate');
const data = await fetchData();
return { props: { data } };
}
2. Optimize Data Fetching
- Use parallel requests when fetching multiple data sources:
export async function getServerSideProps() {
// Fetch data in parallel
const [usersRes, postsRes] = await Promise.all([
fetch('https://api.example.com/users'),
fetch('https://api.example.com/posts')
]);
const [users, posts] = await Promise.all([
usersRes.json(),
postsRes.json()
]);
return { props: { users, posts } };
}
3. Consider Streaming SSR
Next.js 13+ supports streaming SSR, which allows the server to send chunks of the page as they become ready:
// app/posts/[id]/page.js (Next.js 13+ App Router)
import { Suspense } from 'react';
export default function PostPage({ params }) {
return (
<div>
<h1>Post #{params.id}</h1>
<Suspense fallback={<div>Loading post content...</div>}>
<PostContent id={params.id} />
</Suspense>
<Suspense fallback={<div>Loading comments...</div>}>
<Comments postId={params.id} />
</Suspense>
</div>
);
}
Common Pitfalls and Solutions
1. Browser-specific Code
SSR executes code on the server where browser APIs like window
or document
don't exist.
Solution: Use conditional checks or the useEffect
hook:
import { useEffect, useState } from 'react';
function MyComponent() {
const [windowWidth, setWindowWidth] = useState(0);
useEffect(() => {
// This code only runs in the browser
setWindowWidth(window.innerWidth);
const handleResize = () => setWindowWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return <div>Window width: {windowWidth}px</div>;
}
2. Slow Server Response Times
If data fetching takes too long, the user experience suffers.
Solutions:
- Implement timeout limits
- Use skeleton screens or loading states
- Consider using Incremental Static Regeneration instead
3. Large Page Size
SSR can sometimes generate large HTML documents.
Solutions:
- Minimize unnecessary data in props
- Use pagination for large data sets
- Consider component-level streaming
Summary
Server-side Rendering in Next.js offers a powerful way to create fast-loading, SEO-friendly pages with dynamic content. By rendering pages on the server, you can deliver fully formed HTML to users while ensuring search engines can properly index your content.
Key takeaways from this guide:
- SSR renders pages on each request, providing fresh, dynamic content
- Use
getServerSideProps
to fetch data and render pages server-side - SSR is ideal for SEO-critical pages and personalized content
- Performance can be optimized with proper caching and parallel data fetching
- Choose the right rendering method based on your specific use case
Additional Resources
- Next.js Official Documentation on Data Fetching
- Next.js Examples Repository
- Understanding Hydration in React and Next.js
Practice Exercises
-
Basic SSR Page: Create a Next.js page that fetches and displays a list of blog posts using SSR.
-
SSR with Dynamic Routes: Build a product catalog with category pages that use dynamic routing and SSR.
-
Performance Optimization: Take an existing SSR page and implement caching and parallel data fetching to improve performance.
-
Authentication with SSR: Create a protected dashboard page that checks authentication status server-side before rendering.
-
Advanced: Implement a news site that uses SSR for article pages with proper SEO metadata and social sharing tags.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)