Understanding Rendering Methods SSR, CSR, SSG, ISR in NextJS

Introduction

NextJS provides multiple flexible rendering methods, helping optimize performance and SEO for web applications. Below is an introduction to these methods along with their main advantages:

  • Server-Side Rendering (SSR): Data is fetched and rendered into complete HTML on the server for each request. This HTML is then sent to the client. Better for SEO, faster First Contentful Paint (FCP) on the client, suitable for frequently changing data.
  • Client-Side Rendering (CSR): The browser downloads a blank HTML file and a JavaScript file. JavaScript then runs on the client to fetch data and render the content. Smooth user experience after the first load, reduces server load, suitable for pages requiring high interactivity and data that does not need SEO.
  • Static Site Generation (SSG): HTML is pre-rendered at build time (when running the next build command). These static HTML files are served for every request. Extremely high performance, fastest response time, excellent SEO, low server cost, suitable for content that changes infrequently (e.g., blogs, documentation pages).
  • Incremental Static Regeneration (ISR): Allows creating and updating static pages after build (in runtime), based on a configured revalidate time period. This is a combination of SSG's performance with SSR's data update capability, helping keep pages fresh without rebuilding the entire project.

Prerequisites

Note that the code below looks like it's for React but actually only runs on the NextJS framework. First, create a NextJS project before continuing.


Detail

Create file Post.tsx to define 1 Server Component

export function Posts(data: {posts: Post[]}) {
return data.posts.map(post => (
<div key={post.title}>
<h2>{post.title}</h2>
<p className="m-5">{post.body}</p>
</div>
))
}

export interface Post {
title: string
body: string
}


Create file ssg/page.tsx, this default usage is SSG

import {type Post, Posts} from '../Post'

async function getDynamicData(): Promise<Post[]> {
const res = await fetch('https://jsonplaceholder.typicode.com/posts')
return res.json()
}

export default async function SSGPage() {
const data = await getDynamicData()
return (
<div className="m-10">
<Posts posts={data} />
</div>
)
}


Check the result


Create file csr/page.tsx, when you need to use React states, you need to add 'use client' to it

'use client'

import {useEffect, useState} from 'react'
import {type Post, Posts} from '../Post'

export default function CSRPage() {
const [posts, setPosts] = useState<Post[] | null>(null)

useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => res.json())
.then(data => {
setPosts(data)
})
}, [])

if (!posts) return <p>Empty posts</p>

return (
<div>
<h1>Client-Side Rendering</h1>
<Posts posts={posts} />
</div>
)
}



Create file isr/page.tsx, you add the revalidate field, which is the time to cache this page

import {type Post, Posts} from '../Post'

async function getDynamicData(): Promise<Post[]> {
const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
next: {revalidate: 60},
})
return res.json()
}

export default async function ISRPage() {
const data = await getDynamicData()
return (
<div className="m-10">
<Posts posts={data} />
</div>
)
}

When using it, there will be the following cases:

  • When a user accesses without a cache: it will call the API to get data, save it to the cache, and return the content to the user.
  • When a user accesses with an existing cache:
    • Not expired: it will return the cached content to the user.
    • Cache time expired: It still returns the cached content, then runs in the background to call the API to get new content to save to the cache; next time the user accesses, it will return the new cached content.



Create file ssr/page.tsx

import {type Post, Posts} from '../Post'

async function getDynamicData(): Promise<Post[]> {
const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
cache: 'no-store',
})
return res.json()
}

export default async function SSRPage() {
const data = await getDynamicData()
console.log('data', data)
return (
<div className="m-10">
<Posts posts={data} />
</div>
)
}

  • Page content is generated completely from the server side, each request will call the corresponding API, suitable for dynamic content cases that change frequently.
  • You can check the log that the data part is printed from the server side, not in the Console tab of the Browser.



To check, you can build the project to see the results

$ next build
Next.js 16.2.0 (Turbopack)
- Environments: .env

Creating an optimized production build ...
Compiled successfully in 3.5s
Finished TypeScript in 1598ms
Generating static pages using 9 workers (15/15) in 459ms
Finalizing page optimization in 218ms

Route (app) Revalidate Expire
/
/_not-found
/rendering/csr
/rendering/isr 1m 1y
/rendering/ssg
Æ’ /rendering/ssr
/others

(Static) prerendered as static content
Æ’ (Dynamic) server-rendered on demand

Done in 7.76s.

You can see clear annotations for static files for CSR, ISR, SSG and dynamic for server-side rendering only for SSR.


When you go to .next/server/app/, you will see that there are only html files for the corresponding SSG, ISR, CSR, none for SSR, you can open the content to see the details that have been created.

Happy coding!

See more articles here.

Comments

Popular posts from this blog

All Practice Series

Kubernetes Deployment for Zero Downtime

Deploying a NodeJS Server on Google Kubernetes Engine

Setting up Kubernetes Dashboard with Kind

Practicing with Google Cloud Platform - Google Kubernetes Engine to deploy nginx

Using Kafka with Docker and NodeJS

Monitoring with cAdvisor, Prometheus and Grafana on Docker

Kubernetes Practice Series

Sitemap

NodeJS Practice Series