Senyo Labs | Software & Technology Solutions
Back to Blog
Engineering12 readSeptember 28, 2024

Next.js and Server-Side Rendering: A Complete Guide

Explore how Next.js revolutionizes React development with server-side rendering, static generation, and the power of hybrid rendering.

Ebrar Altunkaynak

Ebrar Altunkaynak

Full Stack Engineer

#Next.js#React#SSR#Web Performance
Next.js and Server-Side Rendering: A Complete Guide

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

  1. Improved SEO: Search engines can easily crawl and index your content
  2. Faster Initial Load: Users see content more quickly
  3. Better Performance on Low-End Devices: Less JavaScript execution on the client
  4. 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.