A short history of how we kept moving “the truth” around the web

We spent 15 years moving “the truth” back and forth between the client and the server. From the chaos of loading spinners to the limitations of static generation, React Server Components aren’t just a new feature — they’re an apology. Here is the history of how we finally found balance.️ #ReactJS #WebDev #RSC
If my last post was about realizing I had given the browser too much power, this one is about realizing I wasn’t alone.
Because this wasn’t just my mistake.
It was the web’s.
For the last 15 years, frontend development has been one long, ongoing argument about a single question:
Where does the truth live, and when should we compute it?
Everything else like frameworks, rendering strategies, buzzwords is just fallout from that argument.
So let’s talk about how we got here.
Intro: The Battle for “The Truth”
Every web architecture is a compromise between two things that fundamentally hate each other:
- Latency - how fast can we show something?
- Freshness - how correct is that something?
Optimize for latency, and you risk showing stale or incorrect data.
Optimize for freshness, and you risk making users wait while the system thinks.
For years, developers have been chasing the same impossible ideal:
the speed of a static file with the intelligence of a live database.
So we experimented.
We tried putting logic in the browser.
We tried pre-rendering everything.
We tried computing truth on every request.
Each attempt fixed one problem and created another.
This is the story of those trade-offs — and why React Server Components feel less like a revolution and more like an apology.
Part 1: The Client Era (CSR)

Remember when we thought moving everything to the browser was a good idea? The Client Era gave us “native” feels but paid for it with loading spinners and hydration hell. Part 1 of the history of React Server Components. #WebDev #JavaScript #CSR
This era began with a *bold * idea:
“What if the server did almost nothing?”
The server sent a tiny index.html.
The browser did everything else.
Routing, data fetching, state management, authentication checks, none of it happened until the JavaScript loaded, executed, and figured out what the page was supposed to be.
At the time, this felt like liberation.
- We escaped full-page reloads.
- We escaped server templates.
- We escaped waiting.
Single Page Applications promised an experience that felt native. Clicks were instant. Transitions were smooth. The UI felt alive.
The browser wasn’t just a renderer anymore.
It was the runtime.
But there was a hidden cost.
Before the app could show anything meaningful, the browser had to:
- download the JavaScript bundle
- execute it
- reconstruct the route
- fetch the data
- reconcile state
- hydrate the UI
Only then could the page appear…
So we invented:
- Loading spinners.
- Skeletons.
- Optimistic placeholders.
We called this process hydration , which was a nicer way of saying:
“Please wait while the browser decides what this page actually is.”
On fast devices and good connections, this was tolerable.
On slower phones or unstable networks, it was painful.
Search engines didn’t love it either. They could see the HTML, but the truth lived somewhere inside JavaScript that hadn’t run yet.
CSR didn’t fail because it was naive.
It failed because it asked the browser to “resolve” too many unknowns, too late in the process.
The browser was fast.
It just wasn’t early.
And that tension between speed of interaction and delay of truth is what pushed the web into its next era.
Part 2: The Static Era (SSG & ISR)

We realized loading spinners were bad, so we decided to just... freeze time? The Static Era (SSG) gave us incredible speed, but it came with a catch: The page was fast, but it was often a "beautifully rendered lie." #ReactJS #SSG #WebPerf
After years of watching loading spinners spin like they were paid by the rotation, we had a collective realization:
“What if the page already existed?”
This was the great return to common sense.
Instead of asking the browser to assemble reality at runtime, we rendered pages ahead of time. At build time. Once. On purpose.
Static Site Generation (SSG) gave us something we had forgotten the web was capable of:
Instant pages.
No waiting.
No JavaScript negotiations.
No suspense.
The HTML was already there, sitting on a CDN, waiting patiently for someone to request it. Users clicked a link and BOOM, the page appeared, fully formed, confident, and unbothered.
Blogs loved it.
Docs loved it.
Marketing teams loved it.
The Origami Software Engineer loved it.
Performance metrics went green. SEO tools stopped sending concerned emails. Lighthouse (not that lighthouse) reports finally felt proud of us.
For a while, it felt like we had solved frontend performance forever.
And then reality happened.
Because static pages have one unavoidable flaw:
they are extremely sure of themselves , even when they’re wrong.
A static page is correct…
until the data changes.
And the moment it does…
the page becomes a beautifully rendered ✨ lie ✨.
So we rebuilt.
And rebuilt again.
And watched our CI pipelines age in real time.
Enter Incremental Static Regeneration (ISR).
ISR was static pages with a pulse. A compromise. A way to say:
“This page is probably still correct… but let’s check later.”
Instead of rebuilding the entire site, pages could quietly regenerate in the background. The CDN stayed fast. The content stayed mostly fresh. Everyone felt clever again.
And to be fair it worked.
But only up to a point.
Static systems could decide what pages exist , but not who is asking. Personalization didn’t fit. Authentication didn’t fit. Anything user-specific immediately felt awkward.
✔️ Pre-render a blog post.
❌ Pre-render your bank balance.
SSG and ISR were like Forrest Gump, incredible at speed.
They just weren’t great at context.
And once applications stopped being brochures and started being products, that architectural imposter syndrome became impossible to ignore.
The static era didn’t fail.
It simply reached the edge of what certainty-at-build-time could provide.
And beyond that edge… waited the server.
Part 3: The Dynamic Era (SSR)

Static pages were great until we realized users exist. SSR brought “the truth” back to the server, giving us personalization and authority. But it came with a fatal flaw: The Waterfall. If one thing was slow, everything was slow. Part 3 of the RSC story. #WebDev #SSR #ReactJS
At some point, we had to admit something uncomfortable:
Static pages are great…
but the users…
they ruin EVERYTHING!
Because the moment your page depends on who is asking their account, their permissions, their data pre-rendering stops being an option.
You can’t statically generate:
- a bank balance
- an inbox
- a dashboard
- anything that involves the words “Welcome back”
So we went back to the server.
Server-Side Rendering brought authority with it. For every request, the server could finally compute the truth right now.
Who is this user?
What are they allowed to see?
What does this page actually mean to them?
The server answered all of it, then sent the browser a fully rendered page. No guessing. No negotiation. Just truth.
SEO loved it.
Product managers loved it.
Compliance teams loved it.
The Origami Software Engineer really loved it.
And for a moment, it felt like we had _balance _again.
Then the *bill * arrived.
SSR was correct but it was expensive.
Every request meant:
- authentication
- multiple data fetches
- rendering on the server
- generating HTML from scratch
And worse than cost was latency.
Because SSR had a fatal flaw: The Waterfall.
The server couldn’t send anything until everything was ready. One slow database query, one sluggish API , one mildly upset microservice and the entire page just waited.
The user saw nothing.
The server stared at promises.
Everyone suffered quietly.
You could optimize.
You could cache.
You could take the train from Platform 9¾.
But fundamentally, SSR was all-or-nothing. Fast when everything was fast. Painful when anything wasn’t.
SSR didn’t fail because it was wrong.
It failed because it demanded ✨ perfection ✨.
And real systems, much like real developers rarely deliver that consistently.
We had finally moved truth back to the server.
Now we just needed a way to stop waiting for it all at once.
Part 4: The Modern Synthesis (RSC)

The web finally stops arguing with itself. React Server Components aren’t a revolution, they are a reconciliation. The Server owns the Truth, the Client owns the Interaction, and “Streaming” finally kills the Waterfall. The system is finally calm. Part 4 of 4. #ReactJS #RSC #WebArchitecture
This is the part where the web finally stops arguing with itself.
React Server Components (RSC) didn’t show up shouting,
“Everything before me was wrong.”
They showed up sighing and saying,
“Okay… let’s reorganize .”
Because RSC isn’t a replacement for CSR, SSG, ISR, or SSR.
It’s an admission.
An admission that:
- the server is better at owning truth
- the client is better at interaction
- and forcing one side to do both has been… messy?
The breakthrough was subtle but profound:
What if the server orchestrates the data
and the client just renders what already exists?
Suddenly, responsibilities snapped into focus.
The server fetches data directly.
No API detours.
No serialization gymnastics.
No teaching the browser how to ask the right questions.
The server resolves identity.
Permissions.
Context.
Reality.
And it does it in parallel.
Instead of waiting for:
→ auth → data → more data → you guessed it, even more data
The server says:
“Everyone, fetch at once. We’re adults now.”
This is where The W aterfall dies.
But the real magic isn’t just parallel fetching.
It’s streaming.
For the first time, the server doesn’t wait for everything to be perfect before responding.
Server sends what it has, when it has it.
The shell arrives immediately.
The slow data follows.
The buttons wake up when needed.
One page.
Multiple timelines.
No lies.
Static where it can be.
Dynamic where it must be.
Interactive where it matters.
And the browser?
The browser _finally _relaxes.
It doesn’t have to deduce meaning.
It doesn’t have to defend itself against partial truth.
It doesn’t have to pretend it knows things it doesn’t.
It just renders. Honestly.
This is why RSC feels less like a revolution and more like a reconciliation.
We didn’t invent something new.
We stopped asking the wrong parts of the system to grow up too fast.
The server took responsibility.
The client got its *job * back.
And for the first time in a long time, the system feels… calm.
Conclusion: Choosing Your Tool (and Your Battles)

No era was a mistake. Every rendering strategy was a response to a real problem. The conclusion to our series isn’t about picking the “best” tool, but knowing which battle you’re fighting. Use SSG for content, CSR for interaction, and SSR/RSC when truth absolutely matters. Architectural maturity is knowing the difference. #WebDev #ReactJS #SoftwareArchitecture
By this point, the pattern should be obvious.
No era was a mistake.
Every era was a response.
We didn’t move from CSR to SSG to SSR to RSC because of boredom.
We moved because each solution solved a real problem…
and then politely revealed the next one.
So no, there is no single “correct” rendering strategy.
There never was.
- Use SSG for content that doesn’t argue back.
Marketing pages.
Blogs.
Docs.
Things that are emotionally stable. - Use SSR or RSC for applications where truth actually matters.
Dashboards.
Authenticated experiences.
Anything where “Welcome back” isn’t just decoration. - Use CSR where interaction is the point and correctness is negotiated.
Internal tools.
Admin panels.
Places where a spinner is annoying but acceptable.
Architectural maturity isn’t about picking the newest tool.
It’s about knowing where truth belongs and having the courage to move it there.
If my first post was about realizing I’d made the browser my manager , this one is about realizing the industry did the same thing … and then spent a decade slowly fixing it.
We didn’t arrive at React Server Components by accident.
We got here by breaking things politely, learning slowly, and finally asking the right question:
Not “how fast can this render?”
But “who should be allowed to decide?”
Because systems don’t grow up by adding features.
They grow up by deciding who’s allowed to decide.
And once you make that decision…
That’s not failure.
That’s evolution.
Top comments (0)