Community Article

SSR, CSR, SSG, ISR: Pick the Right One

Four rendering strategies, four cost profiles. Pick by data freshness and personalization needs, not by which acronym sounds most modern.

SSR, CSR, SSG, ISR: Pick the Right One

Four rendering strategies, four cost profiles. Pick by data freshness and personalization needs, not by which acronym sounds most modern.

frontend
performance
react
system-design
sophiegarcia

By @sophiegarcia

February 20, 2026

·

Updated May 20, 2026

788 views

25

4.3 (11)

Four acronyms, four trade-offs, four very different cost models, and a tendency in framework marketing to imply that one of them is universally better than the others. SSR is back, CSR is dead, ISR fixes everything, SSG is for blogs. None of those slogans survive contact with a real product, and most teams I have helped picked the wrong one because they followed the slogan instead of the data.

My stance: the right rendering strategy for a page falls out of two questions, asked in this order. How fresh does the data need to be? How personalized is the page? Once you answer those, the choice between SSG, ISR, SSR, and CSR is mostly forced. The acronyms are not competing philosophies; they are points on a freshness-and-personalization axis, and most apps need more than one of them in the same codebase.

Four acronyms, one decision

Quick definitions
  SSG  Static Site Generation     render at build, ship HTML, never re-render
  ISR  Incremental Static Regen    render at build, refresh on a schedule or event
  SSR  Server-Side Rendering       render on every request
  CSR  Client-Side Rendering       render in the browser, server returns a shell

Every rendering strategy moves the work between three points: build time, request time, and browser time. SSG does it all at build. SSR does it at request. CSR does it in the browser. ISR does it at build with periodic refresh. The trade is between freshness, latency, and per-page cost.

SSG: render at build, serve forever

Static site generation runs the page once at build time and saves the resulting HTML. Every visitor gets the same bytes from a CDN. There is no per-request server work, no database query under load, no rendering bottleneck.

The cost profile is unbeatable for content that does not change between deploys: marketing pages, documentation, blog posts, terms of service, an about page. A page that ships from a CDN edge node in 30 ms with no origin call is faster than any other strategy can be.

The failure mode is staleness. If the content needs to update between deploys, SSG either forces you to redeploy on every change (fine if the build is fast and the changes are infrequent) or accept stale content until the next build. A 50,000-page e-commerce catalog regenerated nightly has 24-hour-stale prices, which is not acceptable for any e-commerce I have worked on.

// Next.js example: a blog post page generated at build
export async function generateStaticParams() {
    const posts = await db.post.findMany({ select: { slug: true } });
    return posts.map((p) => ({ slug: p.slug }));
}

export default async function PostPage({ params }) {
    const post = await db.post.findUnique({ where: { slug: params.slug } });
    return (
        <article>
            <h1>{post.title}</h1>
            <div dangerouslySetInnerHTML={{ __html: post.html }} />
        </article>
    );
}

The tell that SSG is right: the page is the same for every viewer and changes rarely. Marketing site, docs site, content with editorial review cycles measured in days not minutes. SSG wins these flat out.

ISR: render at build, refresh on demand

Incremental Static Regeneration is SSG with a sliding-update mechanism. The page is statically generated and cached, and after a configurable interval (or on a webhook from a CMS, or on a redeploy of a single path) the cache is invalidated and the next request triggers a regeneration in the background.

The sweet spot is content that changes occasionally but not on every request. Product pages on an e-commerce site where prices update a few times a day. A blog index where new posts appear hourly. A pricing page where the marketing team edits weekly. ISR gives you SSG's CDN-fast-as-possible read path with a refresh story that does not require a full deploy.

// Next.js: revalidate this page every 60 seconds
export const revalidate = 60;

export default async function ProductPage({ params }) {
    const product = await db.product.findUnique({ where: { slug: params.slug } });
    return /* ... */;
}

The model that finally clicked for me: ISR is a CDN-with-render-on-miss. The first request after a cache miss pays the rendering cost; subsequent requests in the next 60 seconds get the cached version. If your data does not need to be sub-second fresh, ISR's economics are extraordinary.

Where ISR breaks: pages that need to be different per user. The cache is a single shared cached HTML; if user A and user B should see different content, ISR cannot serve them both from the same cache key. You can shard by URL parameter, but at that point you are doing user-specific caching and the cache hit rate plummets.

SSR: render per request

Server-side rendering runs the component tree on every request, with whatever data the request needs (the user's session, locale-specific content, real-time data). The browser receives complete HTML; the server pays the cost of every page view.

SSR is the right choice when:

  • Each user sees a different page (logged-in dashboard, personalized home).
  • Data must be fresh on every request (live trading prices, sports scores).
  • The page's content depends on the request (search results from a query string).

The cost is throughput. Every page view runs the render path on a server, which competes with every other render and every database query. A site doing 100 requests per second under SSR needs servers that can render 100 pages per second; under SSG the same load barely registers because it is just CDN bytes.

// SSR is the default for any non-revalidate Next.js Server Component that fetches
export default async function DashboardPage() {
    const session = await auth();
    const data = await db.dashboard.forUser(session.userId);
    return <Dashboard data={data} />;
}

A pattern I keep advocating for: do not SSR pages that could be ISR. Logged-out marketing pages, public catalog pages, content pages, all should be SSG or ISR even if your framework defaults to SSR. The default is misleading because SSR works for everything; that does not mean SSR is the right answer for everything.

CSR: render in the browser

Client-side rendering ships an empty HTML shell with a JavaScript bundle that does the entire render in the browser. The server returns roughly the same bytes for every URL, and the browser fetches data and renders the UI on its own.

CSR is the right choice for:

  • Highly interactive apps where the first paint does not need to be fast (internal tools, admin panels, design tools, dashboards behind a login wall).
  • Apps where every screen is dynamic and personalized and SSR would cost too much per request.
  • Apps where the server is genuinely just an API and there is no rendering layer.

The cost is the first paint. Until the bundle downloads and runs, the user sees nothing or a loading spinner. For logged-in apps where the user already loaded the bundle in a previous session and the bundle is cached, this is fine. For a public landing page, it is unacceptable.

// Vite SPA, App.tsx, the entire app is CSR
import { Routes, Route } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

export default function App() {
    return (
        <QueryClientProvider client={queryClient}>
            <Routes>
                <Route path="/" element={<Home />} />
                <Route path="/orders" element={<Orders />} />
            </Routes>
        </QueryClientProvider>
    );
}

The thing teams keep getting wrong with CSR: shipping it for content that should never have been client-rendered. A landing page that takes 2 seconds to first paint because the user has to download 400 KB of JavaScript first is the canonical CSR mistake. If the page is content, not interaction, do not CSR it.

The decision matrix

The matrix I run through for any new page:

QuestionIf yesIf no
Is the content the same for every user?continuejump to SSR or CSR
Does the content change less than once per minute?SSG or ISRcontinue
Is the content highly personalized?SSRCSR
Is the page interactive enough to need the bundle anyway?CSR is fineSSR for first paint

A blog post: same for everyone, changes rarely, SSG.

A product page on an e-commerce site: same for most users, prices update daily, ISR with 5-minute revalidate.

A logged-in dashboard: different per user, SSR (or CSR if the bundle is already cached).

A design tool: highly interactive, CSR with a server that exists only to serve data.

A homepage with personalized recommendations: most of the page is SSG, the recommendations slot is CSR or streaming SSR.

The modern frameworks let you mix these per route, and the right architecture for most products is a mix. The marketing pages are SSG. The product pages are ISR. The logged-in app is SSR or CSR. There is no single right answer for the whole app; there is a right answer for each page.

A failure mode worth naming: choosing for the wrong axis

The mistake I see most: teams that pick a rendering strategy because of TTFB benchmarks without checking whether their actual constraint is data freshness. They benchmark SSR against SSG on cold cache, see SSG winning by 200 ms, and migrate everything to SSG. Six months later they have a content management problem because every editorial change requires a full rebuild, and the performance win is invisible to users who would have been fine with ISR.

The inverse: teams that move from CSR to SSR for SEO reasons and discover their per-request server costs went up tenfold because every search-engine crawl now hits the database. The right answer was probably ISR for the public pages and CSR for the app behind the login.

Pick by the data freshness and personalization questions first. Performance falls out as a consequence; if it does not, you picked wrong on the first questions.

Two rendering rules and a warning

Rule one: build-time work is free at request time. If you can make a page SSG, you should. The user experience is faster, the cost model is cheaper, and the failure modes are simpler (a build either succeeded or failed, there is no "the request crashed mid-render").

Rule two: per-user content needs per-request rendering. Either SSR or CSR. The choice between them comes down to whether the first paint matters. If it does, SSR. If the user is already in the app, CSR is fine.

The warning: do not let the framework's default fool you into thinking the framework chose for you. Next.js's app router defaults to dynamic rendering when it sees a database call without revalidation; that is SSR by default, even for pages that should have been ISR. React Router defaults to CSR; that is fine for an SPA, wrong for a content site. Read what your framework picked for each route, and override it where the page's data shape does not match the default. The right strategy is the one that matches the data, not the one the framework happened to choose.

Back to Articles