There’s a small trick you can do with a rubber band.
You stretch it between your fingers, twist it in a certain way—and suddenly it forms a star. A slight adjustment, just a tiny shift in tension, and now it resembles the Eiffel Tower. Another small move, and it becomes the letter “X”.
Nothing fundamental changed.
Same rubber band. Same fingers. Same material.
Only the arrangement changed.
That’s exactly how modern Next.js works.
One Component, Four Realities
When people first learn Next.js, they often think:
- SSR is one thing
- SSG is another
- ISR is something in between
- CSR is completely different
But that mental model is misleading.
In reality, you can take one component, make tiny changes, and it behaves like four completely different systems.
Let’s ground this in something simple: a product listing page.
You have a component that renders products. Nothing fancy.
The real question is not what it renders.
The real question is:
When is the HTML generated?
The Hidden Question Behind Everything
Every rendering strategy in Next.js answers one core question:
“When should this page be built?”
Let’s walk through the four answers.
Static Site Generation (SSG): Build Once, Serve Forever
Imagine you prepare everything before anyone visits your site.
At build time, your server fetches product data, generates HTML, and stores it.
Now when users visit:
- They don’t trigger computation
- They don’t wait for data fetching
- They just receive ready-made HTML instantly
It’s like printing a book in advance instead of writing it every time someone wants to read.
In App Router, this happens almost silently:
await fetch("https://api.example.com/products");
That’s it. No special API. No ceremony.
By default, Next.js assumes:
“This data probably doesn’t change often. I’ll cache it.”
That assumption is SSG.
Incremental Static Regeneration (ISR): Static, But Alive
Now comes the clever twist.
What if your product list changes occasionally?
SSG alone would fail—you’d be stuck with outdated data unless you rebuild the entire app.
ISR solves this with a simple idea:
“Serve the static page, but quietly update it in the background.”
You add one small instruction:
fetch(url, {
next: { revalidate: 10 }
});
Now the system behaves differently:
- First request → static page
- Next 10 seconds → same cached page
- After 10 seconds → next request triggers regeneration
- Old page is served while a new one is built
- Cache updates silently
No downtime. No blocking. No full rebuild.
This is where the rubber band analogy becomes real:
you didn’t replace SSG—you twisted it slightly.
Server-Side Rendering (SSR): Always Fresh, Always Computed
Now imagine you don’t trust cached data at all.
Every request must reflect the latest state:
- user-specific content
- real-time inventory
- authentication-dependent UI
So you say:
“Never cache. Always compute.”
That’s one line:
fetch(url, {
cache: "no-store"
});
Now every request:
- Hits the server
- Fetches fresh data
- Generates HTML on the fly
This is SSR.
Same component. Same API.
Only one change: cache disabled.
The rubber band didn’t change shape randomly—you pulled it tighter.
Client-Side Rendering (CSR): Let the Browser Do the Work
Now flip the entire model.
Instead of the server preparing HTML, you send almost nothing:
- an empty shell
- JavaScript
- instructions
The browser loads the page, then fetches data itself.
"use client";
useEffect(() => {
fetch(...).then(setData);
}, []);
Now:
- Initial HTML is empty
- Data loads after page load
- Everything happens in the user’s browser
This is CSR.
Not better. Not worse. Just different trade-offs:
- More interactive
- Less SEO-friendly
- Slower initial content
The Illusion of “Different Systems”
At first glance, these feel like four completely different architectures.
But look closer.
Nothing really changed except:
- where code runs (server vs client)
- whether data is cached
- when HTML is generated
That’s it.
The Real Mechanism: Fetch Controls Everything
In the App Router world, everything revolves around one primitive:
fetch(url, options)
That single line determines:
- caching behavior
- rendering strategy
- performance characteristics
- freshness of data
You’re not choosing between “SSR vs SSG” anymore.
You’re choosing:
- cache: "force-cache" → SSG
- next.revalidate → ISR
- cache: "no-store" → SSR
- move to client → CSR
Why ISR Feels Special
Among the four, ISR often feels the most “magical”.
That’s because it introduces time into the equation.
SSG is static.
SSR is immediate.
ISR says:
“Stay static… until it makes sense not to.”
It blends:
- performance of SSG
- freshness of SSR
And it even gives you control beyond time:
You can trigger updates manually:
- after a product is added
- after a CMS update
- via an API endpoint
Now your app reacts to events, not just time.
The Rubber Band Revisited
Let’s go back to the analogy.
You didn’t swap tools.
You didn’t rewrite your component.
You only changed tension and position.
Rendering| What changed
SSG| Default caching
ISR| Add revalidation
SSR| Disable caching
CSR| Move logic to browser
Same codebase. Same UI. Same intent.
Different outcomes.
The Shift in Thinking
Most beginners learn Next.js like this:
“There are four rendering methods. I must choose one.”
That’s outdated thinking.
The modern mental model is:
“Rendering is an outcome of caching strategy.”
You are not picking a mode.
You are configuring:
- when data is fetched
- how long it lives
- where computation happens
Why This Matters in Real Projects
This isn’t just theoretical.
This is exactly why:
- your homepage might feel slow (SSR overuse)
- your products might not update (SSG misuse)
- your UI might flicker (CSR overuse)
Once you understand this model, you stop guessing.
You start designing.
Final Thought
The power of Next.js is not in giving you many APIs.
It’s in letting you reshape the same system with small, precise changes.
Like that rubber band.
At first, it looks like a trick.
Then you realize—
it’s just physics.
And once you understand the tension,
you can make it take any shape you want.

Top comments (0)