coderain guide

How to Use Next.js for Server-Side Rendering with React

React has revolutionized front-end development with its component-based architecture and virtual DOM, but by default, React applications are **client-side rendered (CSR)**. This means the browser downloads a minimal HTML file, fetches JavaScript, and then renders the page—often leading to slower initial load times and poor SEO for content-heavy sites. **Server-Side Rendering (SSR)** addresses these issues by rendering React components on the server for every request, generating a fully populated HTML page that’s sent to the client. This improves performance, SEO, and user experience, especially for dynamic or frequently updated content. While implementing SSR in vanilla React is complex (involving setting up servers, routing, and data fetching), **Next.js**—a production-grade React framework—simplifies the process with built-in SSR support, file-based routing, and intuitive data-fetching methods. In this blog, we’ll explore how to leverage Next.js for SSR, from setup to deployment, with practical examples and best practices.

Table of Contents

  1. What is Next.js?
  2. Setting Up a Next.js Project
  3. Understanding Next.js File-Based Routing
  4. Server-Side Rendering (SSR) in Next.js
  5. SSR vs. Other Rendering Methods in Next.js
  6. Deploying a Next.js SSR Application
  7. Troubleshooting Common SSR Issues
  8. Best Practices for Next.js SSR
  9. Conclusion
  10. References

What is Next.js?

Next.js is a React framework created by Vercel that simplifies building production-ready React applications. It abstracts complex configurations (like Webpack, Babel, and server setup) and provides built-in features to optimize performance, scalability, and developer experience.

Key features relevant to SSR:

  • File-based routing: Pages are defined by files in the pages directory, eliminating the need for manual route configuration.
  • Built-in SSR: Use getServerSideProps to fetch data and render pages on the server for every request.
  • API routes: Create serverless backend endpoints directly in your Next.js app (under pages/api).
  • Automatic code splitting: Reduces bundle sizes by splitting code per page.
  • Image and font optimization: Built-in components (next/image, next/font) to optimize media performance.

Setting Up a Next.js Project

To start using Next.js, ensure you have Node.js (v18.17 or later) installed. Follow these steps:

Step 1: Create a New Next.js App

Run the following command in your terminal to create a new project using create-next-app (Next.js’s official starter tool):

npx create-next-app@latest my-ssr-app  

Step 2: Configure the Project

During setup, you’ll be prompted to configure options like TypeScript, ESLint, Tailwind CSS, etc. For simplicity, select:

  • Would you like to use TypeScript? No (or Yes if you prefer TypeScript)
  • Would you like to use ESLint? Yes
  • Would you like to use Tailwind CSS? No (we’ll use basic CSS for this example)
  • Would you like to use src/ directory? Yes
  • Would you like to use App Router? No (we’ll use the traditional Pages Router for SSR; App Router uses Server Components, which are beyond this guide)
  • Would you like to customize the default import alias? No

Step 3: Run the Development Server

Navigate to your project directory and start the development server:

cd my-ssr-app  
npm run dev  

Visit http://localhost:3000 in your browser. You’ll see the default Next.js welcome page, confirming your setup is successful.

Understanding Next.js File-Based Routing

Next.js uses a file-based routing system where the structure of the src/pages directory directly maps to your app’s routes.

  • src/pages/index.js → Route: / (homepage)
  • src/pages/about.js → Route: /about
  • src/pages/posts/[id].js → Dynamic route: /posts/1, /posts/2, etc. (where [id] is a parameter)

This eliminates the need for a separate routing library (e.g., React Router) and makes adding new pages as simple as creating a new file.

Server-Side Rendering (SSR) in Next.js

In Next.js, SSR is enabled by exporting an async function called getServerSideProps from a page component. This function runs on the server for every incoming request, fetches data, and passes it as props to the page component, which is then rendered to HTML and sent to the client.

How getServerSideProps Works

getServerSideProps is a special Next.js function that:

  • Runs server-side only (never in the browser).
  • Accepts a context object containing request-specific data (e.g., URL parameters, HTTP headers, cookies).
  • Must return an object with a props key (the data to pass to the page component).

Syntax:

// src/pages/example.js  
export default function ExamplePage(props) {  
  return <div>{props.data}</div>;  
}  

export async function getServerSideProps(context) {  
  // Fetch data from an API, database, etc.  
  const res = await fetch("https://api.example.com/data");  
  const data = await res.json();  

  // Pass data to the page via props  
  return {  
    props: { data }, // will be passed to the page component as props  
  };  
}  

Key Notes:

  • getServerSideProps runs on every request, making it ideal for dynamic content that changes frequently (e.g., user-specific data, real-time metrics).
  • The context object includes:
    • params: For dynamic routes (e.g., context.params.id for /posts/[id]).
    • req: The HTTP request object (Node.js IncomingMessage).
    • res: The HTTP response object (Node.js ServerResponse).
    • query: An object containing query string parameters (e.g., ?page=1 → context.query.page).

Practical Example: Dynamic SSR Page

Let’s build a dynamic page that fetches and displays a blog post by ID using SSR. We’ll use the JSONPlaceholder API (a free fake API for testing) to fetch post data.

Step 1: Create a Dynamic Route File

Create a file at src/pages/posts/[id].js. This will handle routes like /posts/1, /posts/2, etc.

Step 2: Add getServerSideProps to Fetch Data

In src/pages/posts/[id].js, add the following code:

// src/pages/posts/[id].js  
export default function PostPage({ post }) {  
  if (!post) {  
    return <div>Loading...</div>;  
  }  

  return (  
    <div style={{ maxWidth: "800px", margin: "0 auto", padding: "2rem" }}>  
      <h1 style={{ fontSize: "2rem", marginBottom: "1rem" }}>{post.title}</h1>  
      <p style={{ fontSize: "1.2rem", lineHeight: "1.6" }}>{post.body}</p>  
    </div>  
  );  
}  

export async function getServerSideProps(context) {  
  // Extract the "id" parameter from the URL  
  const { id } = context.params;  

  try {  
    // Fetch post data from JSONPlaceholder API  
    const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);  
    const post = await res.json();  

    // If the post doesn't exist (e.g., invalid ID), return 404  
    if (!post.id) {  
      return { notFound: true };  
    }  

    // Pass the post data to the page component  
    return {  
      props: { post },  
    };  
  } catch (error) {  
    // Handle errors (e.g., network issues)  
    return { notFound: true };  
  }  
}  

Step 3: Test the Dynamic Route

Visit http://localhost:3000/posts/1 in your browser. You’ll see the title and body of post 1 from the JSONPlaceholder API. Try changing the ID (e.g., /posts/42) to see different posts.

Why This Works:

  • getServerSideProps runs on the server for every request to /posts/[id].
  • It extracts the id parameter from context.params, fetches the corresponding post, and passes it to the PostPage component as props.post.
  • If the post isn’t found (e.g., /posts/9999), Next.js returns a 404 page.

SSR vs. Other Rendering Methods in Next.js

Next.js offers multiple rendering strategies. Here’s how SSR compares to others:

MethodHow It WorksUse CasePerformance
Server-Side Rendering (SSR)Renders on the server for every request.Dynamic content (e.g., user dashboards, real-time data).Slower than SSG (server load per request), but fresh data.
Static Site Generation (SSG)Renders at build time; reuses cached HTML.Static content (e.g., blogs, marketing pages).Fastest (served from CDN), but stale data unless rebuilt.
Incremental Static Regeneration (ISR)SSG + re-renders in the background after a timeout.Static content that needs occasional updates (e.g., product pages).Fast (CDN-cached) with fresh data after regeneration.
Client-Side Rendering (CSR)Renders in the browser after JS loads.Highly interactive apps (e.g., dashboards with client-side state).Poor initial load (blank screen until JS runs).

When to Use SSR:

  • You need data that changes with every request (e.g., user-specific content like a personalized feed).
  • SEO is critical, and content can’t be pre-rendered at build time (e.g., real-time stock prices).

Deploying a Next.js SSR Application

Next.js SSR apps require a server (or serverless functions) to render pages on request. The easiest way to deploy is with Vercel (Next.js’s creator), but other options include Netlify, AWS, or a custom Node.js server.

Deploying to Vercel

Vercel optimizes Next.js apps out of the box, with built-in support for SSR, API routes, and global CDN.

Step 1: Push Your Code to GitHub

Commit your project to a GitHub repository:

git init  
git add .  
git commit -m "Initial commit: SSR post page"  
git branch -M main  
git remote add origin https://github.com/your-username/my-ssr-app.git  
git push -u origin main  

Step 2: Connect to Vercel

  1. Go to Vercel and sign up with your GitHub account.
  2. Click New Project and import your my-ssr-app repository.
  3. Vercel automatically detects Next.js and configures the build settings. Click Deploy.

Within minutes, your app will be live. Vercel provides a URL (e.g., my-ssr-app.vercel.app). Visit https://your-vercel-url/posts/1 to test your SSR page.

Troubleshooting Common SSR Issues

1. getServerSideProps Isn’t Running

  • Cause: The page is being statically optimized (Next.js may statically render pages with no dynamic data).
  • Fix: Ensure getServerSideProps is exported correctly (async function, named export). If using dynamic imports, mark the page as dynamic with ssr: true.

2. Slow Page Loads with SSR

  • Cause: SSR renders on every request, which can slow down if data fetching is unoptimized.
  • Fix:
    • Fetch only necessary data (avoid over-fetching).
    • Use caching (e.g., Redis) for frequently accessed data.
    • Consider ISR if data doesn’t need to be real-time.

3. “Window is Not Defined” Errors

  • Cause: Code that references browser APIs (e.g., window, document) runs on the server, where these APIs don’t exist.
  • Fix: Use typeof window !== 'undefined' to guard browser-specific code, or move it to useEffect.

Best Practices for Next.js SSR

1. Use SSR Sparingly

Only use SSR for pages that require dynamic, per-request data. For static content, prefer SSG or ISR to reduce server load and improve performance.

2. Optimize Data Fetching

  • Fetch data as close to the source as possible (e.g., use a database directly instead of an external API if possible).
  • Parallelize data requests (e.g., Promise.all for multiple independent API calls).

3. Cache Aggressively

  • Cache API responses on the server (e.g., with fetch’s cache: 'force-cache' or a tool like Redis).
  • Use client-side caching (e.g., SWR or React Query) for data that updates after the initial render.

4. Handle Errors Gracefully

  • In getServerSideProps, return { notFound: true } for 404s or { redirect: { destination: '/error' } } for redirects.
  • Use try/catch blocks to handle failed API calls and return fallback data.

Conclusion

Next.js simplifies Server-Side Rendering for React applications, allowing you to build fast, SEO-friendly apps with dynamic content. By leveraging getServerSideProps, you can fetch data on the server for every request and render fully populated pages, improving performance and user experience.

Key takeaways:

  • Next.js’s file-based routing eliminates manual route configuration.
  • getServerSideProps enables SSR by fetching data and passing it to page components.
  • Use SSR for dynamic, per-request content; prefer SSG/ISR for static or semi-static content.
  • Deploy SSR apps easily with Vercel for optimal performance.

References