Next.js page rendering techniques: CSR, SSR, SSG, ISR, or PPR?

Written by
Blog by Stephan Moerman
Stephan Moerman
Published on
Views
Reading time
10 min read
Next.js page rendering techniques: CSR, SSR, SSG, ISR, or PPR?

Understanding the rendering landscape in Next.js

As a modern web development framework, Next.js has revolutionized the way we build React applications by introducing a range of powerful rendering techniques. From the traditional client-side rendering (CSR) to the more advanced server-side rendering (SSR), static site generation (SSG), incremental static regeneration (ISR), and the newly introduced partial pre-rendering (PPR), Next.js offers a comprehensive set of tools to optimize the performance, scalability, and user experience of your web applications.

In this in-depth article, I'll dive into each of these rendering approaches and Next.js build strategies, exploring their benefits, use cases, and how they can be leveraged to create highly efficient and SEO-friendly web applications. Whether you're a seasoned Next.js developer or just starting your journey, this article will provide you with a solid understanding of the different Next.js page rendering techniques and help you make informed decisions when building your next project. I'll also touch on some of the most important Next.js performance optimization techniques.

Client-Side Rendering (CSR)

Let's start with the most familiar rendering approach: client-side rendering (CSR). In a traditional React application, the entire application is rendered in the user's browser, with all the components and their logic running on the client-side. This means that when a user visits your website, the initial page load may be slower as the browser needs to download the entire JavaScript bundle and then execute it to render the content.

While CSR provides a high degree of interactivity and responsiveness, it can be less SEO-friendly, as search engines may have difficulty indexing the content that is generated on the client-side. Additionally, the initial page load can be slower, which can negatively impact user experience, especially for users with slower internet connections.

For client side rendering in Next.js, you can use React hooks:

/app/csr-example.tsx
import { useState, useEffect } from "react";
 
export default function DashboardPage() {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
 
  useEffect(() => {
    async function fetchData() {
      const res = await fetch("/api/dashboard-data");
      const result = await res.json();
      setData(result);
      setIsLoading(false);
    }
 
    fetchData();
  }, []);
 
  if (isLoading) return <div>Loading dashboard data...</div>;
 
  return (
    <div>
      <h1>Dashboard</h1>
      {/* Render dashboard with data */}
      {data.metrics.map((metric) => (
        <div key={metric.id}>
          <h2>{metric.name}</h2>
          <p>{metric.value}</p>
        </div>
      ))}
    </div>
  );
}

This approach defers data fetching to the client, which makes it suitable for personalized or frequently changing data.

Server-Side Rendering (SSR)

To address the limitations of CSR, Next.js introduced server-side rendering (SSR). In an Server-Side Rendering Next.js setup, the initial page load is rendered on the server, and the resulting HTML is then sent to the client. This approach offers several benefits:

  • Improved SEO: Search engines can easily crawl and index the content, as it is already rendered on the server-side. But, there are more Next.js SEO benefits that I won't be covering today.
  • Faster initial load times: Users receive the initial page content immediately, without having to wait for the entire JavaScript bundle to download and execute.
  • Server-side data fetching: Components can fetch data directly on the server, without relying on client-side APIs or state management libraries.

In a Next.js application, the default behavior is for pages to be server-side rendered. This means that the React components that make up your pages are executed on the server, and the resulting HTML is sent to the client. This approach provides a great balance between performance, SEO, and interactivity. If you'd like to take a look at a more in-depth article about CSR vs SSR, I highly recommend you read Prismic's blog about Client-Side Rendering vs Server-Side Rendering.

Here's a simple example of SSR implementation in Next.js:

/app/ssr-example.tsx
export default function ProductPage({ product }) {
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <span>${product.price}</span>
    </div>
  );
}
 
// This function runs on the server for every request
export async function getServerSideProps() {
  // Fetch data from an API
  const res = await fetch("https://api.example.com/products/1");
  const product = await res.json();
 
  // Pass data to the page via props
  return { props: { product } };
}

The getServerSideProps function runs on the server for every request, fetching data dynamically before rendering the page, ensuring fresh content.

Server Components and Client Components

One important concept to understand in Next.js is the distinction between server components and client components. Server components are React components that are rendered on the server-side, while client components are rendered on the client-side.

Server components have several advantages:

  • They can fetch data directly from the server, without the need for client-side state management.
  • They can perform server-side computations and logic without impacting the client-side performance.
  • They are more SEO-friendly, as the content is already rendered on the server.

Client components, on the other hand, are better suited for handling user interactions and dynamic content that requires client-side state management and event handling. These components are rendered in the user's browser and can leverage the full power of React's client-side features, such as hooks and event listeners.

In a Next.js application, you can mix and match server components and client components, allowing you to create the optimal balance between server-side and client-side rendering for your specific use case.

CSR vs SSR rendering in Next.js
CSR vs SSR rendering in Next.js

Static Site Generation (SSG)

While SSR provides significant improvements over CSR, there is still room for further optimization. This is where static site generation (SSG) comes into play. In an SSG setup, the pages of your application are pre-rendered during the build process, and the resulting HTML files are then served to the client.

The key benefits of SSG include:

  • Faster initial load times: Since the pages are pre-rendered, users receive the content immediately, without any server-side rendering required.
  • Improved scalability: The pre-rendered HTML files can be easily cached and served from a content delivery network (CDN), reducing the load on your server.
  • Enhanced SEO: Search engines can easily crawl and index the pre-rendered content, as it is already in the form of static HTML.

In a Next.js application, the default behavior is for pages to be statically generated during the build process. You can identify which pages are statically generated by running the npm run build command and inspecting the output, which will indicate which routes are static and which are dynamic.

However, there are cases where a page cannot be statically generated, such as when it depends on runtime information (e.g., URL parameters, cookies, or headers). In these situations, Next.js will automatically switch to a dynamic rendering approach, where the page is rendered on-demand during runtime.

Incremental Static Regeneration (ISR)

While SSG provides significant performance and scalability benefits, it can be challenging to keep the pre-rendered content up-to-date, especially for pages that change frequently. This is where incremental static regeneration (ISR) comes into play.

ISR allows you to update the pre-rendered content of a page without the need to rebuild the entire application. When a user requests a page that has been marked as ISR-enabled, Next.js will check if the content is still valid. If the content is stale, Next.js will regenerate the page in the background and serve the updated content to the user.

The key benefits of ISR include:

  • Improved content freshness: Pages can be updated without the need for a full rebuild, ensuring that users always see the latest content.
  • Reduced server load: The regeneration process happens in the background, so the user doesn't have to wait for the page to be updated.
  • Seamless user experience: Users continue to see the cached content while the page is being regenerated, providing a smooth and uninterrupted experience.

To enable ISR in your Next.js application, you can use the revalidate option when exporting a page, or the revalidatePath or revalidateTag functions in your server-side code.

Partial Pre-Rendering (PPR)

The latest addition to the Next.js rendering methods is partial pre-rendering (PPR), which takes the concept of static site generation even further. With PPR, you can selectively pre-render parts of a page, while leaving other parts to be rendered dynamically.

The key benefits of PPR include:

  • Optimal performance: The pre-rendered parts of the page can be served quickly, while the dynamic parts are rendered on-demand, providing the best of both worlds.
  • Improved scalability: The pre-rendered parts of the page can be cached and served from a CDN, reducing the load on your server.
  • Flexible content management: You can mix and match pre-rendered and dynamically rendered content within the same page, allowing for a more granular control over the rendering process.

To implement PPR in your Next.js application, you can use the experimental Suspense component to define the boundaries between the pre-rendered and dynamically rendered parts of your page. This feature is a powerful tool for optimizing the performance and scalability of your Next.js applications.

Warning: Partial Pre-Rendering is an experimental feature. I highly advice against using it in any type of production application as it may cause issues.

Partial Pre-Rendering (PPR) with Next.js - image by Vercel
Partial Pre-Rendering (PPR) with Next.js - image by Vercel

Choosing the Right Rendering Approach

When building a Next.js application, it's important to carefully consider the rendering approach that best fits your use case, as part of following Next.js development best practices. Here are some general guidelines to help you make the right choice:

  • Client-Side Rendering (CSR): Use CSR for highly interactive applications that require a lot of client-side state management and event handling, such as dashboards or single-page applications.
  • Server-Side Rendering (SSR): Use SSR for applications that require good initial load times, SEO, and server-side data fetching, such as content-heavy websites or blogs.
  • Static Site Generation (SSG): Use SSG for content-heavy websites or blogs where the content doesn't change frequently, as it provides the best performance and scalability.
  • Incremental Static Regeneration (ISR): Use ISR for content-heavy websites or blogs where the content changes more frequently, as it allows you to update the pre-rendered content without the need for a full rebuild.
  • Partial Pre-Rendering (PPR): Use PPR for applications that have a mix of static and dynamic content, allowing you to optimize the performance and scalability of your application.

Remember, you can also mix and match these rendering approaches within the same Next.js application, allowing you to create the optimal balance between performance, SEO, and interactivity for your specific use case. Pre-Rendering in Next.js is a common technique that you can find plenty of resources for on the internet to get you started.

Conclusion

In this comprehensive blog post, we've explored the various rendering approaches available in Next.js, from the traditional client-side rendering to the more advanced server-side rendering, static site generation, incremental static regeneration, and partial pre-rendering. By understanding the strengths and use cases of each approach, you can make informed decisions when building your next Next.js application, ensuring that it delivers the best possible performance, scalability, and user experience.

Happy coding, and may your Next.js applications be fast, SEO-friendly, and a delight to use!