Upskills Community
Upskills
upskills.dev
Join our community on Discord to get help about the tutorials and ask your questions.
@upskillsdev
Checkout our GitHub to find the source code and submit any issues or feature requests.
Chapter 2: Modern Front-End Development with React
This is part of the Indie Dev Toolkit series on modern tools for solo full-stack developers. We trace the full arc of React rendering — from the server-rendered pages and jQuery era, through the SPA revolution, to today's Server Components — drawing on real production experience to cut through the hype.
You'll walk through SPA, SSR, SSG, and RSC with interactive diagrams and specific examples, then see how real-world apps mix these patterns together — so you can pick the right approach based on your users' needs, not just what's trending.
Prerequisites
Evolution of React Rendering
Tutorial Content
6 sections • approximately 1 hour
Welcome to Part 1 of the Modern Front-End Development series! Before we dive into specific rendering patterns, let's take a step back and understand how we got here — because the history of React rendering isn't just trivia. It's the key to understanding why each pattern exists and when you should use it.
The Server-Rendered Web (Before 2010)
I started my career as a .NET MVC developer. Back then, the dominant rendering model for web applications was server-side: the server did everything. You wrote your business logic in C# controllers, your HTML in Razor views, and every single user interaction triggered a full round-trip to the server. If you've worked with ASP.NET MVC, PHP, or Ruby on Rails, you know exactly what I'm talking about.
(Yes, AJAX was already enabling partial updates in apps at that time. But it was more of an add-on for specific interactions rather than the core way of building apps. The mental model was still "server renders pages, AJAX is just a sprinkle on top.")
Here's what my typical day looked like — writing controllers that fetched data and returned complete HTML pages:
// HomeController.cspublic class HomeController : Controller{public ActionResult Index(){ViewBag.Title = "Dashboard";var stats = _statsService.GetDashboardStats();return View(stats); // Server generates full HTML}[HttpPost]public ActionResult Search(string query){var results = _searchService.Find(query);return View("SearchResults", results); // Full page reload}}
<!-- Index.cshtml (Razor View) -->@model DashboardStats<h1>@ViewBag.Title</h1><p>Total Users: @Model.TotalUsers</p><form method="post" action="/Home/Search"><input name="query" placeholder="Search..." /><button type="submit">Go</button> <!-- Full page reload --></form>
Every interaction I built followed the same cycle:
This model worked — and honestly, it was simple. I didn't have to think about client-side state, JavaScript bundling, or hydration. The server was the single source of truth. But after building several projects this way, I kept running into the same pain points:
UpdatePanel, or complex AJAX patterns that felt like duct tape on top of a model that wasn't built for itMaking It Interactive: The jQuery Era
Of course, we didn't just accept those limitations. Before the SPA revolution, there was a middle ground — and if you were building web apps in the late 2000s, you almost certainly lived in it: jQuery on top of server-rendered pages.
The idea was simple. Keep the server in charge of rendering HTML, but sprinkle in jQuery to make things feel interactive. Need a dropdown menu? jQuery plugin. Want to submit a form without a full page reload? $.ajax(). Need to show/hide a section? $('.panel').slideToggle(). It was pragmatic, and honestly, it felt like magic at the time.
Here's what that looked like in a typical ASP.NET MVC app — the server still rendered the page, but jQuery handled the interactions:
<!-- ProductList.cshtml -->@foreach (var product in Model.Products){<div class="product-card" data-id="@product.Id"><h3>@product.Name</h3><span class="price">$@product.Price</span><button class="btn-add-to-cart">Add to Cart</button><div class="cart-feedback" style="display:none;">Added!</div></div>}<div id="cart-count">Cart: <span>0</span> items</div><script>// The "interactive layer" — jQuery on top of server HTML$(document).ready(function () {var cartCount = 0;$('.btn-add-to-cart').click(function () {var $card = $(this).closest('.product-card');var productId = $card.data('id');$.ajax({url: '/Cart/Add',method: 'POST',data: { id: productId },success: function () {cartCount++;$('#cart-count span').text(cartCount);$card.find('.cart-feedback').fadeIn().delay(1000).fadeOut();}});});});</script>
This worked well enough for adding interactivity to individual pages. But as applications grew more ambitious — real-time updates, complex forms, multi-step wizards — the cracks started to show:
$('#cart-count span').text(). This made complex state synchronization a nightmarejQuery wasn't the problem — it was excellent at what it was designed for: making DOM manipulation easy in an era of browser inconsistencies. The problem was that we were trying to build applications with a tool designed for enhancements. We needed something fundamentally different — a way to build entire user interfaces in the browser, with real state management and a component model.
That's exactly what the first wave of SPA frameworks set out to solve.
The First Wave of SPAs (2010–2013)
Before React existed, several frameworks pioneered the idea of moving rendering from the server to the browser:
Minimal MVC structure for the browser — Models, Views, and Routers brought server-side patterns to client code.
Declarative data bindings — your UI updated automatically when the underlying data changed. No manual DOM wiring.
Full framework with two-way data binding, dependency injection, and client-side routing — all batteries included.
Convention-over-configuration — a batteries-included SPA framework inspired by Ruby on Rails.
These frameworks proved that SPAs were viable. They moved rendering to the browser and gave users the smooth, no-reload experience that server-rendered apps couldn't deliver — and major tech companies adopted them for production applications.
But they all shared common problems: complex state management, unpredictable DOM updates, and performance degradation as applications grew. Two-way data binding — the signature feature of AngularJS and Knockout — made it easy to build small demos but nightmarish to debug in large applications because any piece of data could be modified from anywhere.
Enter React: A Different Philosophy (2013)
Facebook was facing these exact problems at scale. Their News Feed and Chat UIs were becoming impossible to maintain with existing tools — data changed frequently, UI state was complex, and two-way bindings led to cascading updates that were hard to reason about.
In May 2013, Facebook Engineers unveiled React at Facebook Seattle. Rather than competing feature-for-feature with AngularJS or Ember, React took a radically different approach to the same SPA rendering model:
What React optimized for SPA development:
map, filter, and ternaries instead of framework-specific template directivesThe mental model was simple: state changes → React re-renders → only the changed parts of the DOM update. No full-page reloads, no server round-trips for every interaction. React wasn't the first SPA framework, but it solved the right problems — and that's why it won.
The SPA Golden Age and Its Limits
After React's rise, the industry went all-in on client-side rendering. The prevailing wisdom became: "Server rendering is dead. SPAs are the future."
But as SPAs scaled to production, developers discovered significant trade-offs:
| Problem | Why It Happens |
|---|---|
| SEO challenges | Google can render JS, but indexes it in a delayed second wave — SSR content gets indexed immediately, more reliably, and works with all search engines and social link previews |
| Slow initial load | Users download and parse large JS bundles before seeing anything |
| Loading spinners | Data fetching starts after the app loads — users see spinners first |
| Bundle bloat | All code ships to the browser, even code the user may never execute |
| Waterfall requests | Component A loads → fetches data → renders Component B → fetches more data |
Sound familiar? Server rendering had solved most of these years ago. The community just forgot why.
So... Back to the Server?
Starting around 2016, the React ecosystem began exploring ways to bring server rendering back — but not the old "generate everything on the server" approach of C# MVC. Instead, a new generation of hybrid solutions emerged that combined the best of both worlds.
Notice the pattern: the industry didn't simply go back to server rendering. Each new approach kept what SPAs got right (interactivity, component model, client-side state) while bringing back what SPAs struggled with (fast initial loads, reliable SEO indexing, less JavaScript shipped to the browser).
Why This Tutorial Exists
Here's what I've learned after years of working with React — first as a developer working with .NET C# MVC, then following React and the JS ecosystem evolve through SSR, SSG, and now Server Components:
I'll walk through each pattern — SPA, SSR, SSG, Server Components, and hybrid approaches — one section at a time, so you can see where each one shines and where it falls short.
The goal of this tutorial is simple: understand each rendering pattern deeply enough to make the right choice for your specific project — not because a tech influencer said so, not because it's trending, but because you understand the trade-offs and can justify your decision.
Let's start by diving into where it all began for React: Client-Side Rendering.
About the author
NAB, Software Engineer
Hi, I'm Vu, a Software Engineer at NAB (National Australia Bank) with a love for creating web and mobile apps that don't just look cool but feel great to use. I've had the chance to work with some awesome companies over the years, picking up new tricks and tackling many kinds of challenges along the way.
Now, I'm thrilled to share what I've learned and help others through fun, interactive coding tutorials!