SSR vs SSG vs ISR — Part 3: The Hybrid Reality

If you read Part 1 and Part 2 carefully, you already know the “pure” forms:
Server Side Rendering (SSR) does work during every request.
Static Site Generation (SSG) does work once at build time.
Incremental Static Regeneration (ISR) does work at build time and occasionally in the background.
In most real-world applications with dynamic data, these strategies aren’t used in isolation.
In reality, production code uses all three deliberately, based on data consistency requirements, performance goals, and user expectations.
This part is less about definitions and more about architectural thinking and practical usage.
Let’s dig in.
The E-Commerce Product Page That Broke My Brain
Imagine this route:
/products/airpods-pro
In Part 1 and Part 2, we treated these rendering modes in isolation.
But a real product page is not a textbook example.
In production, a product page typically contains:
Static product metadata (title, description, images)
Pricing that changes frequently
Discounts or promotions
Stock availability that can change in seconds
Reviews that grow over time
Personalisation (e.g., user-specific pricing)
Now ask yourself:
Should the entire product page be SSR?
Should it be SSG?
Should it be ISR?
If you answered one of the above, you’re still thinking in terms of knobs — not goals.
Let’s shift that.
Performance + Freshness = Partial Responsibility
In production, people care about:
First Contentful Paint (FCP)
Largest Contentful Paint (LCP)
Data freshness
Scalability
Cost
SEO
And any sane team will divide information based on how frequently it changes.
Some parts change once a month.
Others change every second.
Treating them the same is wasteful.
So the real architecture looks like this:
Product Page
├── Static Content (SSG/ISR)
├── Frequent Dynamic Data (Client Fetch / SSR API)
├── Personalised Content (Client Fetch / SSR API)
Let’s unpack this through a real sequence.
Static Layout — Pre-rendered for Speed and SEO
The parts that don’t change often — product title, description, images — go through:
SSG if they’re truly static
ISR if they need occasional refresh
Build time or background regeneration
Example (App Router):
export const revalidate = 60 // only if product info changes periodically
async function fetchProductData(slug) {
return await getProduct(slug)
}
export default async function Page({ params }) {
const product = await fetchProductData(params.slug)
return (
<div>
<h1>{product.name}</h1>
<img src={product.image} />
<p>{product.description}</p>
{/* The rest will come later */}
</div>
)
}
This section benefits from:
Static rendering → very fast initial HTML
Improved SEO → search bots see full description
Low cost → no work on every request
In DevTools:
HTML loads with minimal TTFB
No blocking work for static content
This alone increases perceived performance.
Behind the Scenes — Why Static Alone Is Not Enough
But in an e-commerce site, static content is never enough.
Because prices and stock change constantly.
If your static HTML says:
Price: ₹19,999
Stock: Available
But right now:
Price: ₹18,499
Stock: Sold out
Then you have technical SEO correctness issues, conversion loss, or worse — legal issues for incorrect pricing.
That’s when you stop generating HTML and start calling APIs.
Client-Side Fetching: Fresh Data, Fast Static
Once the static HTML is delivered, we fill in the dynamic pieces on the client:
"use client"
import { useEffect, useState } from "react"
function ProductDynamic({ slug }) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
async function fetchLiveData() {
const res = await fetch(`/api/products/${slug}/live-data`)
const json = await res.json()
setData(json)
setLoading(false)
}
fetchLiveData()
}, [slug])
if (loading) return <p>Loading latest price...</p>
return (
<>
<p>Price: ₹{data.price}</p>
<p>Stock: {data.stock}</p>
</>
)
}
export default ProductDynamic;
Fresh data
No blocking HTML generation
Instant response
Separation of concerns
And this fetch happens in the browser:
Browser → GET /api/products/airpods-pro/live-data
Which means:
Price and stock are up to date
API returns correct values from DB
No server rendering needed for the entire page
Only relevant data is fetched
You’ll see this in DevTools:
HTML loads instantly
Fetch/XHR calls fill in dynamic content after HTML
This is how hybrid actually feels.
The Revalidation API — The Missing Piece
Static content can become stale, and waiting for a timer is often not acceptable.
Imagine:
A flash sale just started
A discount just went live
Inventory suddenly dropped
Waiting 60 seconds for ISR to regenerate is not great.
So production systems often use:
On-Demand Revalidation API
Next.js provides this for exactly this purpose.
Instead of waiting for a 60-second revalidate window, we tell Next.js exactly when to regenerate a page.
Here’s how it looks:
// app/api/revalidate/route.js
import { revalidatePath } from "next/cache"
export async function POST(req) {
const { slug } = await req.json()
revalidatePath(`/products/${slug}`) // regenerate just that page
return NextResponse.json({ revalidated: true })
}
Now when the admin updates the price:
The admin panel updates the DB
It calls:
await fetch('/api/revalidate', { method: 'POST', body: JSON.stringify({ slug }) })Next.js runs the revalidation function, deletes the cached HTML for that path, and the next incoming request triggers regeneration of the page.
All future requests serve fresh static HTML
With this:
No waiting for timer
No stale pages for long windows
Precise control over regeneration
This is how production sites keep static pages fresh without blocking users.
Why Production Sites Use This Hybrid Pattern
Let’s revisit the key trade-offs:
SSR
Always fresh
Expensive per request
Higher TTFB
Slow at scale
SSG
Very fast
Pre-rendered
Great SEO
Static until next build
ISR
Static most of the time
Fresh occasionally
Low cost
Good compromise
Hybrid (SSG/ISR + Client Fetch + Revalidation)
Static for layout
Fresh dynamic data
On-demand regeneration
Scales well
Fast responses
This pattern aligns with production requirements:
Fast initial load
Correct content
Scalable infrastructure
SEO-friendly output
When a new price is set:
Layout was static
Dynamic fetch pulls in real pricing
Revalidate API regenerates static output
No stale content lives long
This is not hypothetical.
This is how teams actually build commerce platforms today.
Observability in DevTools
When exploring this hybrid pattern:
HTML (Doc)
Built static
Instant load
Minimal TTFB
Fetch / XHR
Client requests dynamic data
Price and stock arrive separately
Revalidation Trigger
Revalidate API call
You won’t see it in Doc timing
But you notice updated HTML next request
This separation is the architectural signature of hybrid systems.
Final Reflection
The real confusion around SSR, SSG, and ISR doesn’t come from the concepts themselves.
It comes from the fact that no real production system can be explained with only one strategy.
It’s not:
“This page is SSR”
or
“This page is SSG”
It’s:
“This page’s layout is static,
this page’s dynamic content is fetched,
and we regenerate only when needed.”
That is the hybrid reality.
And it’s only visible when you stop thinking in definitions,
and start thinking about:
timelines
execution models
when work happens
how content stays fresh
Which is exactly why we built this series.



