CSR, SSR, SSG and ISR in Next.js explained with examples (App Router and Pages Router)

My Thought

Well, I believe having a clear understanding of how CSR, SSR, SSG and ISR work in Next.js is the most of your Next.js knowledge. I asked ChatGPT and read some other write ups where developers are saying that they choose NextJS because of its features like server side rendering, client side rendering, flexibility of different ways to enhance the performance of web apps and its SEO-friendliness. In fact recruiters ask questions related to these topics like how much you understand the concepts and how to get the most out of these features. So, I want to make sure everything of this topic gets crystal clear through this write up.

Prerequisites: It's clear that here I am not teaching you Next.js, nor does this write-up cover everything about the framework. It's an important part of NextJS. So, having at least a basic understanding of Next.js is essential.

What is rendering in the first place

Before learning different rendering strategy it's important to understand what is rendering in the first place. Rendering is the process of generating and displaying the content of a web page, including HTML, CSS, and JavaScript, to the end user's browser. Efficient rendering is essential for fast page loads and a seamless user experience.

CSR (Client-Side Rendering)

In client-side rendering the server delivers a minimal HTML file to the client and the content is generated in the browser using JavaScript.

In CSR, the initial load of a webpage can be slower because the browser downloads the necessary JavaScript files first and then render the content. Once the JavaScript is loaded, it takes over, fetching data from APIs and rendering the page. This approach provides a seamless and dynamic user experience, making CSR a popular choice for single-page applications (SPAs) and web apps that require frequent updates.

CSR in NextJS

By default every component of NextJS was client component until NextJS 13th version got released. In fact, in 13th version's Pages Router all components are client component by default. But when it's App Router (App Router is recommended), you have to mention 'use client' on top to make the entire component a client component.

'use client'

type CompProps = {
    first: "first_text"
    second: "second_text"
}

const Comp: React.FC<CompProps> = ({first, second}) => {
    return (
        <h1>Component</h1>
    )
}

export default Comp

Keys to Emphasize

  1. useEffect function, this function is the key indicator that a page is using Client-Side Rendering.

  2. LOADING indicator, because the data fetching runs after the page is rendered, the data is not fetched instantly, therefore showing a loading state.

  3. Data is fetched on every page request, which is why the time shown is different for each reloads.

these points taken from Theodorus Clarence's post

Examples of Applications

  1. Social Media Platforms: Websites like Twitter and Facebook employ CSR to provide real-time updates and dynamic interactions.

  2. Single-Page Applications (SPAs): SPAs, like Gmail and Trello, leverage CSR for a fluid user experience.

SSR (Server-Side Rendering)

Server-side rendering (SSR) is a way of creating web pages where the server makes the HTML for a page and sends it to the user's browser. This has several benefits: it helps with search engine optimization (SEO), makes the first load faster, and improves performance for users with slower devices. With SSR, the server creates the page's HTML before sending it to the user, so the user sees the page quickly and search engines can read the content easily.

However, SSR can use a lot of server resources because the server has to create the HTML for every request. This can put a lot of strain on the server. Also, SSR might not be the best choice for pages with content that doesn't change often, since the extra work for the server might not be worth it.

Examples of Applications

  1. E-commerce Websites: Online stores like Amazon and eBay often use SSR to ensure SEO optimization and fast loading times for product pages.

  2. News Portals: News websites, including The New York Times and BBC, leverage SSR to provide up-to-date news articles with SEO benefits.

SSR in Next.js

Pages Router

First of all you need to remember that before releasing Next.js 13th version pages router was the default router system.

In Pages Router, you can perform Server-Side Rendering (SSR) actions by using the getServerSideProps function. This function allows you to fetch data and render the page on the server for every request. That means you get a ready-made html file.

// pages/index.js

import React from 'react';

export async function getServerSideProps() {
  // Fetch data from an API or perform any server-side logic
  const res = await fetch('https://api.example.com/data');
  const data = await res.json();

  // Pass the data to the page via props
  return {
    props: {
      data,
    },
  };
}

const HomePage = ({ data }) => {
  return (
    <div>
      <h1>Server-Side Rendered Page</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
};

export default HomePage;

Now, the gotten data from getServerSideProps is included to the component as a prop.

App Router

In Next.js 13, the App Router introduces a new way to structure and manage your application. So, if you are not aware of that, you must learn about the folder structure first. Once you know everything about folder structure of app router, then jump into performing SSR using app router.

To perform Server-Side Rendering (SSR) using the App Router, you will typically use the new app directory along with the fetch function directly within React components. That means every component in app router is a server component by default.

// app/page.js

import React from 'react';

// Define an async function to fetch data
async function fetchData() {
  const res = await fetch('https://api.example.com/data');
  if (!res.ok) {
    throw new Error('Failed to fetch data');
  }
  return res.json();
}

// Define the Page Component
export default async function Page() {
  const data = await fetchData();

  return (
    <div>
      <h1>Server-Side Rendered Page with App Router</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

Here as it's seen clearly we can fetch data directly inside the component. Unlike client-component, we don't need to mention whether it's a server component or not.

Keys to Emphasize

  1. getServerSideProps function, this function is the key indicator that a page is using Server-Side Rendering (for pages router).

  2. DELAY before render, and no LOADING indicator, the data is fetched before the page is rendered, so there will be a slight delay where the API is being hit at the moment, then it will show the page without loading indicator

  3. Data is fetched on every page request, which is why the time shown is different for each reloads.

SSG (Static Site Generation)

Static-site generation (SSG) creates all the pages of a website when you build it, turning them into static HTML files. These files can be served quickly from a content delivery network (CDN). SSG provides great performance and SEO benefits because the HTML files are pre-made and ready to serve. This method is especially good for websites with a lot of content, like blogs, online stores, and landing pages.

However, SSG might not be the best for websites with many pages because the build process can take a long time, slowing down development. Also, SSG isn't ideal for websites with content that changes often, as it doesn't support real-time updates easily.

SSG in Next.js

Pages Router

There are two methods, getStaticProps() and getStaticPaths(), that help create static pages. If you have a page that shows all posts, you can use getStaticProps() to fetch the data and build that page. On the other hand, if you want to create a page for each individual post, you use getStaticPaths() along with getStaticProps(). This way, you can generate static pages for each post separately.

// pages/posts/[id].js

import React from 'react';

// Fetch data for each post at build time
export async function getStaticProps({ params }) {
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`);
  const post = await res.json();

  return {
    props: {
      post,
    },
  };
}

// Specify which pages to generate at build time
export async function getStaticPaths() {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts');
  const posts = await res.json();

  // Get the paths we want to pre-render based on posts
  const paths = posts.map(post => ({
    params: { id: post.id.toString() },
  }));

  // We'll pre-render only these paths at build time.
  return {
    paths,
    fallback: false, // can also be true or 'blocking'
  };
}

// The page component
const Post = ({ post }) => {
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </div>
  );
};

export default Post;

This is a page component with a dynamic route. It uses the getStaticPaths function to generate paths. Each path includes params object. For each path, the getStaticProps function receives this params object to fetch the necessary data. As the example clearly shows params object has id. This process generates all the pages at build time.

App Router

when you use App Router, you actually ensure Static Site Generation (SSG) by fetching data. Because when you fetch data within App Router way, it caches fetched data and generates static pages. if you don't mention { cache: "force-cache" } explicitly, it will still default to SSG automatically. Though explicitly mentioning { cache: "force-cache" } will also ensure it is SSG. However, when you need SSR, you can specify { cache: "no-store" } in the fetch request to ensure it works in an SSR way.

// app/posts/page.js

import React from 'react';

// Fetch posts data at build time
async function fetchPosts() {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
    cache: 'force-cache' // This ensures SSG behavior
  });
  if (!res.ok) {
    throw new Error('Failed to fetch posts');
  }
  return res.json();
}

export default async function PostsPage() {
  const posts = await fetchPosts();

  return (
    <div>
      <h1>Posts</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>
            <a href={`/posts/${post.id}`}>{post.title}</a>
          </li>
        ))}
      </ul>
    </div>
  );
}

when using the App Router, you ensure Static Site Generation (SSG) by fetching data during the build process in a way that the fetched data is cached and used to generate static pages. The default behavior for fetch is { cache: 'force-cache' }, which is suitable for SSG. However, if you don't provide a second argument or specify { cache: 'no-store' }, the behavior would be more aligned with Server-Side Rendering (SSR).

ISR (Incremental Static Regeneration)

Incremental Static Regeneration (ISR) is a modern rendering strategy that combines the benefits of Server-Side Rendering (SSR) with the ability to cache and serve static pages. ISR allows you to pre-render pages at build time and update them incrementally in response to user requests.

One of the key features of ISR is the ability to specify a revalidation time for each page. Revalidation time determines how often a page is regenerated and updated.

https://res.cloudinary.com/practicaldev/image/fetch/s--MIOZBf3y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9bieofbwwt3kv9jd61yc.png

Add the following piece of code to the SSG component to make it ISR:

// ISR settings
export const revalidate = 60; // Revalidate the page every 60 seconds

Last Words

I have tried to explain all the rendering procedure concisely with plain English. I hope you have had something to take from this read.

6
Subscribe to my newsletter

Read articles from Abeer Abdul Ahad directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Abeer Abdul Ahad
Abeer Abdul Ahad

I am a Full stack developer. Currently focusing on Next.js and Backend.