React is a popular JavaScript library for building user interfaces, primarily focused on component-based development. By default, React applications use Client-Side Rendering (CSR), where the browser handles rendering the UI after downloading JavaScript bundles. However, when combined with frameworks like Next.js (which is built on React), developers gain access to more advanced rendering strategies that optimize performance, SEO, and user experience. Next.js extends React by providing server-side capabilities, static generation, and hybrid approaches.
The strategies mentioned—SSR, SSG, ISR, CSR, RSC, and PPR—address how and when HTML is generated and delivered to the client. They balance trade-offs like load times, interactivity, data freshness, and server load. Below, I’ll explain each in detail, their relation to React and Next.js, pros/cons, and provide small code examples (using Next.js where applicable, as it’s the primary framework for these features).
1. CSR (Client-Side Rendering)
Explanation: In CSR, the server sends a minimal HTML skeleton (often just a root <div>) along with JavaScript bundles. The browser then executes the JavaScript to fetch data, render components, and populate the UI. This is React’s default behavior in apps created with Create React App (CRA). Next.js supports CSR as a fallback or for specific pages/components, but it’s less emphasized in favor of server-optimized methods. CSR is great for highly interactive apps (e.g., SPAs like dashboards) but can suffer from slower initial loads and poor SEO, as search engines see empty HTML initially.
Relation to React/Next.js: Core to vanilla React. In Next.js, you can opt into CSR by using hooks like useEffect for data fetching on the client, or by disabling server rendering for a page/component.
Pros: Full interactivity without server involvement after initial load; easy to implement dynamic updates.
Cons: Slower Time to First Paint (TTP); bad for SEO; higher client-side compute.
Small Example (Vanilla React or Next.js page with client-side fetching):
// pages/index.js in Next.js (or App.js in React)
import { useState, useEffect } from 'react';
export default function Home() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/data') // Or external API
.then(res => res.json())
.then(setData);
}, []);
return (
<div>
{data ? <p>Data: {data.message}</p> : <p>Loading...</p>}
</div>
);
}
Here, the page renders “Loading…” initially, and data is fetched/rendered in the browser.
2. SSR (Server-Side Rendering)
Explanation: With SSR, the server generates the full HTML for a page on each request, including data fetching if needed. The browser receives ready-to-display HTML, which improves initial load times and SEO (search engines can crawl the content). After the HTML loads, React “hydrates” it on the client to add interactivity. Next.js makes SSR easy with getServerSideProps, while vanilla React requires a server setup (e.g., with Node.js/Express).
Relation to React/Next.js: React supports SSR via libraries like react-dom/server. Next.js natively enables it per-page, making it hybrid with CSR (client takes over after hydration).
Pros: Fast initial render; excellent SEO; dynamic data per request.
Cons: Higher server load; slower for high-traffic sites; TTFB (Time to First Byte) can be longer if data fetching is slow.
Small Example (Next.js page):
// pages/ssr.js
export default function SSRPage({ data }) {
return <p>Data from server: {data.message}</p>;
}
export async function getServerSideProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return { props: { data } };
}
On each request, the server fetches data and renders HTML. The client hydrates for interactivity.
3. SSG (Static Site Generation)
Explanation: SSG pre-renders pages at build time into static HTML files, which are served from a CDN. Data is fetched during the build (e.g., from APIs or files), making it ideal for content that doesn’t change often (e.g., blogs, docs). No server computation per request—pages are fast and cheap to host. Next.js uses getStaticProps for this; vanilla React doesn’t natively support SSG without tools like Gatsby.
Relation to React/Next.js: Next.js excels at SSG, generating static sites from React components. It’s a build-time optimization on top of React.
Pros: Blazing fast loads; low server costs; great SEO and scalability.
Cons: Stale data if content changes post-build; requires rebuilds for updates; not for user-specific dynamic content.
Small Example (Next.js page):
// pages/ssg.js
export default function SSGPage({ data }) {
return <p>Static data: {data.message}</p>;
}
export async function getStaticProps() {
const res = await fetch('https://api.example.com/static-data');
const data = await res.json();
return { props: { data } };
}
At build time (npm run build), HTML is generated. Deployed files serve instantly without server runtime.
4. ISR (Incremental Static Regeneration)
Explanation: ISR is a hybrid of SSG and SSR. Pages are pre-rendered at build time (like SSG), but Next.js allows regeneration in the background after a “revalidation” period (e.g., every 60 seconds) or on-demand. If a request comes in after the period, it serves the stale version while regenerating a fresh one for future requests. This keeps static performance with dynamic freshness.
Relation to React/Next.js: Exclusive to Next.js (introduced in v9.3). Builds on React’s rendering but adds Vercel/Next.js-specific caching.
Pros: Static speed with automatic updates; reduces build times for large sites.
Cons: Potential for stale data during revalidation; still requires a serverless/hosting setup like Vercel.
Small Example (Next.js page, extending SSG):
// pages/isr.js
export default function ISRPage({ data }) {
return <p>Data (updates every 60s): {data.message}</p>;
}
export async function getStaticProps() {
const res = await fetch('https://api.example.com/dynamic-data');
const data = await res.json();
return {
props: { data },
revalidate: 60, // Revalidate every 60 seconds
};
}
Initial build generates static HTML. On requests after 60s, it regenerates in the background.
5. RSC (React Server Components)
Explanation: RSC allows components to run entirely on the server, fetching data and rendering without sending JavaScript to the client for those parts. Only interactive (client) components are bundled and hydrated. This reduces bundle sizes and shifts compute to the server. Introduced in React 18, but Next.js integrates it seamlessly in App Router (v13+). Non-interactive parts stay server-only.
Relation to React/Next.js: A React feature, but Next.js App Router makes it practical with streaming and suspense. Differs from SSR by being component-level, not page-level.
Pros: Smaller client bundles; secure data fetching (API keys stay server-side); better performance for data-heavy apps.
Cons: Requires server for rendering; learning curve; can’t use client hooks (e.g., useState) in server components.
Small Example (Next.js App Router, server component fetching data):
// app/rsc/page.js (server component by default)
import { Suspense } from 'react';
import ClientComponent from './ClientComponent'; // A client component
async function fetchData() {
const res = await fetch('https://api.example.com/data');
return res.json();
}
export default async function RSCPage() {
const data = await fetchData();
return (
<div>
<p>Server-rendered data: {data.message}</p>
<Suspense fallback={<p>Loading interactive part...</p>}>
<ClientComponent /> {/* 'use client' at top of file */}
</Suspense>
</div>
);
}
The page/component runs on server; only <ClientComponent> sends JS to client.
6. PPR (Partial Prerendering)
Explanation: PPR is a Next.js 14+ feature that prerenders static parts of a route at build time (like SSG) while leaving dynamic parts to render on the server at request time (like SSR/RSC). It uses suspense boundaries to stream dynamic content, combining static speed with dynamic flexibility. Ideal for e-commerce pages with static layouts but dynamic user data.
Relation to React/Next.js: Builds on RSC and React Suspense. Exclusive to Next.js App Router, enhancing hybrid rendering.
Pros: Optimal performance (static shell loads instantly, dynamics stream in); seamless UX.
Cons: Newer feature; requires careful suspense setup; hosting must support streaming.
Small Example (Next.js App Router with PPR):
// app/ppr/page.js
import { Suspense } from 'react';
async function DynamicPart() {
const res = await fetch('https://api.example.com/user-data');
const data = await res.json();
return <p>Dynamic: {data.name}</p>;
}
export default function PPRPage() {
return (
<div>
<p>Static part: This loads instantly.</p>
<Suspense fallback={<p>Loading dynamic...</p>}>
<DynamicPart /> {/* Renders on server at request time */}
</Suspense>
</div>
);
}
Static shell prerenders at build; <DynamicPart> renders/streams on request.
Here’s a clean, easy-to-compare table of all rendering strategies in React & Next.js:
| Property | CSR | SSR | SSG | ISR | RSC (React Server Components) | PPR (Partial Prerendering) |
|---|---|---|---|---|---|---|
| Full name | Client-Side Rendering | Server-Side Rendering | Static Site Generation | Incremental Static Regeneration | React Server Components | Partial Prerendering (Next.js 14+) |
| HTML generated | In browser after JS loads | On every request (server) | At build time | Build time + background refresh | On server (build or request) | Static shell at build + dynamic holes at request |
| Data fetching location | Client only | Server (per request) | Build time only | Build + optional revalidate | Server only (never sent to client) | Static → build, Dynamic → request |
| SEO friendly | Poor | Excellent | Excellent | Excellent | Excellent | Excellent |
| First load speed | Slow | Fast | Very fast | Very fast | Very fast (minimal client JS) | Fastest (static shell + streaming) |
| Requires server at runtime | No | Yes | No | Yes (only for revalidation) | Yes | Yes (only for dynamic parts) |
| Rebuild/revalidation needed | Never | Never (fresh on each hit) | Yes, full rebuild | No, auto background refresh | No | No |
| Typical use case | Dashboards, SPAs | User profiles, news with cookies | Blogs, docs, marketing pages | News, product listings | Any page wanting tiny JS bundles | E-commerce pages, personalized feeds |
| Next.js implementation | useEffect, ‘use client’ | getServerSideProps or async server component | getStaticProps | getStaticProps + revalidate: n | Default in App Router (no ‘use client’) | App Router + <Suspense> + experimental ppr |
| Small code hint | useEffect(() => fetch...) | getServerSideProps | revalidate: undefined | revalidate: 60 | async function Page() { const data = await fetch... } | Static text + <Suspense><Dynamic/></Suspense> |
Quick Decision Table (What should I use?)
| Use Case | Recommended Strategy |
|---|---|
| Blog / Documentation / Marketing site | SSG or ISR |
| User dashboard (private, interactive) | CSR or RSC + Client Components |
| Personalized page (user profile) | SSR or PPR |
| Product page with reviews & user cart | PPR (static layout + dynamic parts) |
| High-traffic page that updates hourly | ISR |
| Need to hide API keys, reduce JS | RSC (Server Components) |
| Want maximum performance + freshness | PPR (cutting-edge, Next.js 14+) |
Current Best Practice (2025)
Most modern Next.js apps use a mix:
App Router (Next.js 13+)
├─ Layouts & pages → React Server Components (RSC) by default
├─ Static parts → automatically prerendered (PPR in Next.js 14+)
├─ Dynamic/personalized parts → wrapped in <Suspense>
└─ Interactive parts → 'use client' components
This gives you the best of all worlds automatically with almost zero configuration.
Let me know if you want a visual diagram version too!
Summary of Relations and When to Use
- React Core: Focuses on CSR, with SSR/RSC as extensions.
- Next.js Enhancements: Adds SSG, ISR, PPR for static/dynamic hybrids; integrates RSC deeply.
Use CSR for interactive apps, SSR/ISR for dynamic SEO-heavy sites, SSG for static content, RSC/PPR for optimized modern apps. In Next.js, mix them per-page/route for best results (e.g., static blog with dynamic comments). For production, consider hosting (Vercel for Next.js) and performance metrics like Core Web Vitals.
