The Developer's Quarterly · Web ArchitectureVol. IV · July 2026

Server Rendering Solutions:SSR, SSG, ISR, and Streaming in Real Projects

Modern React teams do not pick one rendering model for an entire app, they combine SSR, SSG, ISR, and streaming per route based on freshness, personalization, and cost.

Server rendering is no longer one technique. In modern React and Next.js systems, you can pick from multiple rendering strategies depending on content volatility, personalization needs, infrastructure cost, and latency targets.

This guide maps the main server rendering solutions used in production today: SSR, SSG, ISR, and streaming SSR. The goal is practical choice, not abstract theory.

Why this is an architecture decision

Rendering strategy is now a per-route decision. A pricing page, a product detail page, and a user dashboard rarely share the same freshness and personalization requirements.

1 Data volatility

How often does the page data change in real-world traffic?

2 Personalization depth

Is content shared publicly or shaped by user/session context?

3 Cost tolerance

Can the origin sustain request-time rendering under peak load?

SSR on every request

SSR renders HTML for each incoming request. It is a strong fit when data must be fresh and the response is personalized.

Next.js request-time SSR
// src/app/articles/page.tsx import { ArticleList } from '@/features/articles/article-list'; export default async function ArticlesPage() { const response = await fetch('https://example.com/api/articles', { cache: 'no-store', }); if (!response.ok) { throw new Error('Failed to load articles'); } const articles = await response.json(); return <ArticleList items={articles} />; }
Strength Fresh and personalized output at request time.
Tradeoff Higher origin CPU and sensitivity to traffic spikes.
Best fit Dashboards, account pages, role-aware interfaces.
Operational note Caching policy is mandatory, not optional.

SSG at build time

SSG pre-renders pages during build and serves static files through CDN caches. It is often the fastest and cheapest delivery path for public, stable content.

Static generation with route params
// src/app/docs/[slug]/page.tsx import { notFound } from 'next/navigation'; export async function generateStaticParams() { const response = await fetch('https://example.com/api/docs/slugs'); const slugs = await response.json(); return slugs.map((slug) => ({ slug })); } export default async function DocPage({ params }) { const { slug } = await params; const response = await fetch('https://example.com/api/docs/' + slug, { cache: 'force-cache', }); if (response.status === 404) { notFound(); } if (!response.ok) { throw new Error('Failed to load document'); } const doc = await response.json(); return <article>{doc.title}</article>; }

SSG reduces runtime compute dramatically, but it introduces a freshness boundary: content stays fixed until a rebuild or revalidation event.

ISR for bounded staleness

ISR combines static speed with controlled updates. Pages are cached, then regenerated after a defined revalidation window.

Timed revalidation
// src/app/blog/page.tsx export const revalidate = 300; export default async function BlogPage() { const response = await fetch('https://example.com/api/blog', { next: { revalidate: 300 }, }); if (!response.ok) { throw new Error('Failed to load blog posts'); } const posts = await response.json(); return ( <main> <h1>Blog</h1> <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> </main> ); }
On-demand revalidation endpoint
// src/app/api/revalidate/route.ts import { NextRequest, NextResponse } from 'next/server'; import { revalidatePath } from 'next/cache'; export async function POST(request: NextRequest) { const secret = request.headers.get('x-revalidate-token'); if (secret !== process.env.REVALIDATE_TOKEN) { return NextResponse.json({ ok: false }, { status: 401 }); } revalidatePath('/blog'); return NextResponse.json({ ok: true }); }

ISR is ideal when content can tolerate a known staleness budget, such as five minutes, while still benefiting from CDN-level response speed.

Streaming SSR with Suspense

Streaming SSR sends HTML in chunks so users see useful content before every data source completes. Suspense boundaries are the core control mechanism.

Streaming independent page sections
// src/app/dashboard/page.tsx import { Suspense } from 'react'; import { RevenuePanel } from '@/features/admin/revenue-panel'; import { AlertsPanel } from '@/features/admin/alerts-panel'; export default function DashboardPage() { return ( <main> <h1>Operations Dashboard</h1> <Suspense fallback={<p>Loading revenue...</p>}> <RevenuePanel /> </Suspense> <Suspense fallback={<p>Loading alerts...</p>}> <AlertsPanel /> </Suspense> </main> ); }
Model Freshness Cost Profile Typical Use
SSG Build-time Lowest runtime cost Marketing pages, docs, evergreen content
ISR Time-windowed Low to medium Public content with periodic updates
SSR Request-time Medium to high Personalized or strict-freshness views
SSR + Streaming Request-time, partial delivery Medium to high Composite dashboards and operations screens

Production failure modes to avoid

1 SSR by default everywhere

Origin costs and latency rise quickly when static-eligible routes stay dynamic.

2 ISR without invalidation

Editorial urgency and cache windows conflict unless on-demand revalidation exists.

3 Streaming with weak UX boundaries

Too many fragmented loaders create visual noise instead of perceived speed gains.

A rollout plan that works

A practical migration sequence is: classify routes by volatility and personalization, move obvious static routes to SSG, adopt ISR where bounded staleness is acceptable, keep SSR for authenticated surfaces, and add streaming to slow composite pages.

Measure route groups separately. Track cache hit ratio, server render duration, and hydration issues so rendering decisions stay data-driven over time.

Bottom line

Server rendering solutions are complementary tools, not mutually exclusive camps. Most high-performing React platforms are intentionally hybrid.

Use SSG when content is stable, ISR when bounded staleness is acceptable, SSR when personalization demands request-time rendering, and streaming when full-page blocking hurts perceived speed.