Next.js and Server-Side Rendering: A Complete Guide
Next.js has become the go-to framework for building production-ready React applications. One of its most powerful features is its flexible rendering strategies, including Server-Side Rendering (SSR).
What is Server-Side Rendering?
Server-Side Rendering (SSR) is the process of rendering web pages on the server instead of in the browser. When a user requests a page, the server generates the full HTML and sends it to the client.
Benefits of SSR
- Improved SEO: Search engines can easily crawl and index your content
- Faster Initial Load: Users see content more quickly
- Better Performance on Low-End Devices: Less JavaScript execution on the client
- Social Media Sharing: Proper meta tags for rich previews
Next.js Rendering Strategies
Next.js 15 offers several rendering strategies:
1. Server-Side Rendering (SSR)
Pages are generated on each request:
// app/products/page.tsx async function getProducts() { const res = await fetch('https://api.example.com/products', { cache: 'no-store' // Force dynamic rendering }); return res.json(); } export default async function ProductsPage() { const products = await getProducts(); return ( <div> <h1>Products</h1> {products.map(product => ( <div key={product.id}>{product.name}</div> ))} </div> ); }
2. Static Site Generation (SSG)
Pages are generated at build time:
// app/blog/[slug]/page.tsx export async function generateStaticParams() { const posts = await fetch('https://api.example.com/posts').then(res => res.json()); return posts.map(post => ({ slug: post.slug, })); } export default async function BlogPost({ params }) { const post = await fetch(`https://api.example.com/posts/${params.slug}`) .then(res => res.json()); return ( <article> <h1>{post.title}</h1> <div>{post.content}</div> </article> ); }
3. Incremental Static Regeneration (ISR)
Update static pages after build without rebuilding the entire site:
async function getData() { const res = await fetch('https://api.example.com/data', { next: { revalidate: 3600 } // Revalidate every hour }); return res.json(); } export default async function Page() { const data = await getData(); return <div>{data.title}</div>; }
Server Components vs Client Components
Next.js 15 introduces React Server Components by default:
Server Components (Default)
// app/components/ServerComponent.tsx // No 'use client' directive - this is a Server Component export default async function ServerComponent() { const data = await fetch('https://api.example.com/data'); const json = await data.json(); return <div>{json.title}</div>; }
Benefits:
- Zero JavaScript bundle size
- Direct database/API access
- Better security (API keys stay on server)
Client Components
'use client'; import { useState, useEffect } from 'react'; export default function ClientComponent() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}> Count: {count} </button> ); }
When to use:
- Interactive features (onClick, onChange)
- Browser APIs (localStorage, window)
- State management (useState, useReducer)
- Effects (useEffect)
Data Fetching Best Practices
1. Parallel Data Fetching
async function Page() { // These fetch in parallel const [user, posts] = await Promise.all([ fetch('https://api.example.com/user'), fetch('https://api.example.com/posts') ]); return ( <div> <UserProfile data={user} /> <PostsList data={posts} /> </div> ); }
2. Request Deduplication
Next.js automatically dedupes identical requests:
async function getUserData() { return fetch('https://api.example.com/user'); } // These calls are automatically deduped async function Component1() { const user = await getUserData(); // Only fetches once return <div>{user.name}</div>; } async function Component2() { const user = await getUserData(); // Uses cached result return <div>{user.email}</div>; }
3. Streaming and Suspense
Show content progressively as it loads:
import { Suspense } from 'react'; async function SlowComponent() { await new Promise(resolve => setTimeout(resolve, 3000)); return <div>Slow content loaded!</div>; } export default function Page() { return ( <div> <h1>My Page</h1> <Suspense fallback={<div>Loading...</div>}> <SlowComponent /> </Suspense> </div> ); }
Performance Optimization
1. Image Optimization
import Image from 'next/image'; export default function Page() { return ( <Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority // Load immediately placeholder="blur" /> ); }
2. Font Optimization
import { Inter } from 'next/font/google'; const inter = Inter({ subsets: ['latin'] }); export default function Layout({ children }) { return ( <html lang="en" className={inter.className}> <body>{children}</body> </html> ); }
Conclusion
Next.js provides a powerful and flexible framework for building modern web applications. Its hybrid rendering approach lets you choose the best strategy for each page, optimizing for performance, SEO, and user experience.
Whether you need the SEO benefits of SSR, the performance of SSG, or the flexibility of ISR, Next.js has you covered. Combined with React Server Components, it's never been easier to build fast, scalable applications.
Start with Server Components by default, add client-side interactivity only where needed, and leverage Next.js's automatic optimizations to deliver the best possible experience to your users.
