Implementing Server-Side Rendering (SSR) with Next.js: A Step-by-Step Guide

Table of Contents
Big thanks to our contributors those make our blogs possible.

Our growing community of contributors bring their unique insights from around the world to power our blog. 

Delivering dynamic, SEO-friendly pages while maintaining fast load times is a common challenge in modern web development. Next.js’ built-in Server-Side Rendering (SSR) capability lets you fetch and render data on the server for every request—ensuring users and search engines see fully hydrated HTML on first load. In this guide, you’ll learn what SSR is, why it matters, and exactly how to implement it in your Next.js projects. We’ll cover setup, getServerSideProps, dynamic routes, performance considerations, and real-world examples so you can start leveraging SSR today.

What Is Server-Side Rendering?

Server-Side Rendering (SSR) means generating HTML on the server for each incoming request, rather than in the browser. When a user navigates to a page:

  1. The Next.js server runs your React components and data-fetching logic.
  2. It returns fully rendered HTML to the client.
  3. The client hydrates that HTML into a live React app.

SSR vs. CSR vs. SSG

Rendering MethodWhen It RunsProsCons
CSR (Client-Side)In-browserFast transitions, rich interactivityPoor SEO, blank initial load
SSROn every requestSEO-friendly, dynamic data on loadHigher server load, slower TTFB
SSG (Static Gen.)Build timeSuper fast, CDN-cachedNot ideal for frequently changing data

Benefits of SSR with Next.js

  • SEO & Social Sharing: Search engines and social crawlers receive fully rendered pages, boosting indexability and share-card accuracy.
  • Dynamic Content: Ideal for user-specific or real-time data—dashboards, personalized feeds, or data-driven pages.
  • Reduced Client Work: Offloads initial data fetching and rendering to the server, improving perceived performance on low-power devices.
  • Built-In Support: Next.js abstracts complex webpack and routing configuration; you just export a function.

Getting Started with Next.js

If you haven’t already, install Next.js and initialize a project:

bashCopyEditnpx create-next-app@latest my-ssr-app
cd my-ssr-app
npm run dev

Your project structure will include a pages/ directory. Any file here becomes a route:

bashCopyEdit/pages
  index.js        → /
  about.js        → /about
  posts/[id].js   → /posts/:id

Implementing SSR with getServerSideProps

To enable SSR for a page, export an async function named getServerSideProps from your page component. Next.js will run this on the server for every request.

jsCopyEdit// pages/posts/[id].js
import React from 'react';

export async function getServerSideProps(context) {
  const { id } = context.params;
  const res = await fetch(`https://api.example.com/posts/${id}`);
  const post = await res.json();

  // Handle 404
  if (!post) {
    return { notFound: true };
  }

  return {
    props: { post }, // passed to the page component
  };
}

export default function PostPage({ post }) {
  return (
    <article className="prose mx-auto p-4">
      <h1>{post.title}</h1>
      <p className="text-gray-600">By {post.author}</p>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

Key Points

  • context Object: Contains params, req, res, query, and more. Use context.req.headers.cookie for auth.
  • Returning 404s: Return { notFound: true } to render the 404 page.
  • Redirects: Return { redirect: { destination: '/login', permanent: false } } to programmatically redirect.

Dynamic Routes with SSR

Next.js dynamic routes work seamlessly with SSR. Simply name your file [slug].js or [...catchAll].js and use context.params:

jsCopyEdit// pages/category/[...slug].js
export async function getServerSideProps({ params }) {
  const path = params.slug.join('/');
  const data = await fetch(`https://api.example.com/categories/${path}`);
  return { props: { data } };
}

export default function CategoryPage({ data }) { /* ... */ }

Handling Authentication on the Server

Protecting SSR pages often involves checking cookies or session tokens:

jsCopyEditexport async function getServerSideProps({ req }) {
  const token = req.cookies.token;
  if (!token) {
    return { redirect: { destination: '/login', permanent: false } };
  }
  const user = await verifyToken(token);
  return { props: { user } };
}

Optimizing SSR Performance

Because SSR runs on every request, consider these best practices:

  • Cache with HTTP Headers: Set Cache-Control on responses to allow CDNs or browsers to reuse SSR HTML briefly.
  • Memoize Heavy Computations: Use in-memory caches (e.g., LRU) for repeated data.
  • Incremental Static Regeneration (ISR): For semi-dynamic pages, SSG with revalidation (getStaticProps + revalidate) can reduce server load.
  • Lightweight Payloads: Only fetch and send essential data to minimize serialization costs.

Real-World Example: Building an SSR-Powered Blog

  1. List Page (/posts) jsCopyEdit// pages/posts/index.js export async function getServerSideProps() { const res = await fetch('https://api.example.com/posts?limit=10'); const posts = await res.json(); return { props: { posts } }; }
  2. Detail Page (/posts/[id])
    As shown earlier, fetch individual post data in getServerSideProps.
  3. Global Layout
    Use _app.js to wrap pages with a layout and pass user auth status fetched server-side: jsCopyEdit// pages/_app.js import Layout from '../components/Layout'; export default function App({ Component, pageProps }) { return ( <Layout user={pageProps.user}> <Component {...pageProps} /> </Layout> ); }

Common Pitfalls and Best Practices

  • Over-SSR’ing: Don’t SSR every page—use SSG or CSR for static or highly interactive views.
  • Blocking on Slow APIs: Use parallel Promise.all or delegate non-critical data to client side.
  • Exposing Secrets: Never expose private API keys in getServerSideProps; keep them in environment variables.
  • Hydration Mismatches: Ensure server-rendered HTML matches client-side render (avoid Math.random() or browser-only APIs).

Alternatives: When to Use SSG or CSR

  • SSG with ISR: Best for most marketing pages or product listings that update infrequently.
  • CSR: Ideal for highly interactive widgets or user-driven dashboards where SEO is less critical.
  • Hybrid: Combine SSR for critical data and CSR for non-critical parts (e.g., comments section).

Conclusion

Server-Side Rendering with Next.js unlocks SEO-friendly, fast-loading pages that fetch fresh data on every request. By leveraging getServerSideProps, you can build dynamic routes, handle authentication securely, and optimize the user experience across devices. Remember to balance SSR with SSG and CSR, apply caching and performance tweaks, and avoid exposing secrets on the server. With these techniques in your toolkit, you’re ready to deliver robust, data-driven React applications that delight users and search engines alike.

Let's connect on TikTok

Join our newsletter to stay updated

Sydney Based Software Solutions Professional who is crafting exceptional systems and applications to solve a diverse range of problems for the past 10 years.

Share the Post

Related Posts