Skip to main content

Rendering Pattern

Rendering pattern means deciding where and when a page is generated and where data is fetched.

In Next.js, CSR, SSR, and SSG solve the same problem, but they differ in timing, execution place, initial HTML, SEO, and performance.

Rendering Pattern = When + Where data is fetched and shown on the UI

One-line idea: Rendering Pattern = Choose CSR, SSR, SSG, ISR, RSC, or Client Components based on SEO, freshness, interactivity, and performance.

Core Concepts​

Rendering Categories​

Rendering patterns are mainly split between client and server.

Client -> CSR

Server -> SSR / SSG

App Router -> React Server Components + Client Components

In simple terms:

  • CSR renders in the browser.
  • SSR renders on the server for every request.
  • SSG renders at build time.
  • RSC runs components on the server by default.
  • Client Components are used when interactivity is needed.

Client-Side Rendering​

CSR stands for Client-Side Rendering.

In CSR, the page first loads in the browser, then JavaScript fetches data and renders the UI.

Users may initially see a loading state before the real content appears.

CSR = Browser loads page shell -> JS fetches data -> Browser renders UI

Client-Side Rendering: CSR Example​

import { useEffect, useState } from "react";

export default function CsrPage() {
const [data, setData] = useState(null);

useEffect(() => {
fetch("http://localhost:3000/courses")
.then((res) => res.json())
.then(setData);
}, []);

if (!data) return <p>Loading...</p>;

return (
<div>
{data.map((course) => (
<h2 key={course.id}>{course.title}</h2>
))}
</div>
);
}

Client-Side Rendering: CSR Use Case​

CSR is best suited for highly interactive UI.

Good examples:

  • dashboards
  • filters
  • search pages
  • UI where data depends on user actions

CSR downside:

  • slower initial content load
  • weaker SEO because content is not present in the initial HTML

Server-Side Rendering​

SSR stands for Server-Side Rendering.

In SSR, data is fetched on the server for every request.

The server sends fully rendered HTML to the browser, so users see complete content earlier.

SSR = Request -> Server fetches data -> Server renders HTML -> Browser receives content

Server-Side Rendering: SSR Example​

export async function getServerSideProps() {
const res = await fetch("http://localhost:3000/courses");
const data = await res.json();

return {
props: { data },
};
}

export default function SsrPage({ data }) {
return (
<div>
{data.map((course) => (
<h2 key={course.id}>{course.title}</h2>
))}
</div>
);
}

Server-Side Rendering: SSR Use Case​

SSR is useful when:

  • data changes frequently
  • SEO is important
  • page content should be fresh per request

SSR tradeoff:

  • higher server load
  • slower response than static pages because rendering happens per request

Static Site Generation​

SSG stands for Static Site Generation.

In SSG, data is fetched at build time and static HTML files are generated.

Those static files are then served quickly to users.

SSG = Build time data fetch -> Static HTML generated -> Browser receives static page

Static Site Generation: SSG Example​

export async function getStaticProps() {
const res = await fetch("http://localhost:3000/courses");
const data = await res.json();

return {
props: { data },
};
}

export default function SsgPage({ data }) {
return (
<div>
{data.map((course) => (
<h2 key={course.id}>{course.title}</h2>
))}
</div>
);
}

Static Site Generation: SSG Use Case​

SSG is ideal for content that does not change often.

Good examples:

  • blogs
  • marketing pages
  • public content
  • documentation pages

SSG is usually the fastest option because the page is already generated.

Practical Examples​

Incremental Static Regeneration​

ISR stands for Incremental Static Regeneration.

ISR allows a static page to update in the background after a specific time.

revalidate: 60;

This is useful when you want static page speed but still need occasional content updates.

SSG = static until rebuild
ISR = static page + background updates

CSR vs SSR vs SSG​

FeatureCSRSSRSSG
ExecutionBrowserServer per requestBuild time
Data FetchingAfter initial renderBefore response is sentDuring build
Initial HTMLMinimal shellFully populated HTMLStatic pre-rendered HTML
TTFBFast shellSlower due to server processingVery fast
LCPSlower because content depends on JSGood because HTML contains contentBest because content is pre-generated
HydrationFull app hydrationHTML + hydrationStatic HTML + hydration
SEOWeakStrongStrong
CachingBrowser cache / client stateNeeds explicit cachingNaturally cacheable with CDN
ScalabilityHigh client-side loadLimited by server capacityExtremely high
FreshnessReal-time client fetchFresh per requestStale unless rebuilt or ISR is used
Best FitInteractive apps and dashboardsDynamic content and personalizationBlogs, marketing pages, public content
Worst FitSEO-heavy pagesHigh-traffic static pagesFrequently changing real-time data

React Server Components​

In Next.js App Router, components run on the server by default.

This means no JavaScript is sent to the browser unless it is needed.

RSC improves performance by moving data fetching and rendering to the server.

Server Component = Data fetching + rendering on server

React Server Components: Server Component Example​

export default async function Page() {
const res = await fetch("http://localhost:3000/courses");
const courses = await res.json();

return (
<div>
{courses.map((course) => (
<h2 key={course.id}>{course.title}</h2>
))}
</div>
);
}

Server Component behavior:

  • data is fetched on the server
  • no client JavaScript is sent for this part
  • initial render is fast

Client Components​

Client Components are used when browser interactivity is needed.

To create a Client Component, use:

"use client";

Client Components can handle:

  • state
  • events
  • input changes
  • user interactions

Client Components: Client Component Example​

"use client";

import { useState } from "react";

export default function Filter() {
const [query, setQuery] = useState("");

return (
<input
placeholder="Search courses..."
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
);
}

Client Component tradeoff:

  • enables interactivity
  • increases bundle size
  • requires hydration

Combined Server and Client Usage​

A common pattern is to fetch and render data on the server, then use Client Components only for interactive parts.

import Filter from "./Filter";

export default async function Page() {
const res = await fetch("http://localhost:3000/courses");
const courses = await res.json();

return (
<div>
<Filter />

{courses.map((course) => (
<h2 key={course.id}>{course.title}</h2>
))}
</div>
);
}
Server -> data + rendering
Client -> interaction

This keeps the page fast while still allowing interactive UI.

How to Choose​

NeedBest Pattern
Highly interactive UICSR
Fresh data on every requestSSR
SEO with dynamic dataSSR
Static public contentSSG
Static speed with periodic updatesISR
Less JavaScript and server-side data fetchingRSC
State, events, and browser interactionClient Component

Summary​

Rendering Pattern = Choose CSR, SSR, SSG, ISR, RSC, or Client Components based on SEO, freshness, interactivity, and performance.

CSR -> Browser renders
SSR -> Server renders per request
SSG -> Build generates static HTML
ISR -> Static HTML updates in background
RSC -> Server component by default
Client Component -> Browser interactivity