Cộng đồng Upskills
Upskills
upskills.dev
Tham gia cộng đồng Discord để được hỗ trợ về các bài hướng dẫn và đặt câu hỏi.
Vu Nguyen
@nphivu414
Theo dõi tôi trên X để nhận cập nhật và tin tức mới nhất về Upskills.
Chương 2: Modern Front-End Development with React
Đây là phần trong series Indie Dev Toolkit về các công cụ hiện đại cho indie full-stack developers. Chúng ta sẽ đi qua hành trình của React rendering — từ thời trước cả khi React ra đời - server-rendered pages và jQuery, qua cuộc cách mạng SPA, đến Server Components ngày nay.
Bạn sẽ đi qua từng pattern SPA, SSR, SSG, và RSC với interactive diagrams và ví dụ cụ thể, rồi xem cách các ứng dụng thực tế kết hợp các patterns này — để chọn approach phù hợp dựa trên nhu cầu người dùng và constraints của dự án, chứ không phải theo hype.
Yêu cầu
Sự phát triển của React Rendering
Nội dung bài hướng dẫn
6 phần • khoảng 1 giờ
Chào mừng bạn đến với Phần 1 của series Modern Front-End Development! Trước khi đi sâu vào từng rendering strategies cụ thể, hãy nhìn lại quá trình phát triển này — vì lịch sử của React rendering không chỉ là lý thuyết suông. Đó là chìa khóa để hiểu tại sao mỗi pattern ra đời và khi nào bạn nên dùng nó.
The Server-Rendered Web (Trước năm 2010)
Mình bắt đầu sự nghiệp với vai trò một .NET MVC developer. Hồi đó, mô hình rendering phổ biến nhất cho web app là server-side: server làm tất cả mọi thứ. Bạn viết business logic trong C# controllers, HTML trong Razor views, và mỗi tương tác của người dùng đều kéo theo một full round-trip lên server. Nếu bạn đã từng làm với ASP.NET MVC, PHP, hay Ruby on Rails, bạn biết chính xác mình đang nói gì.
(Đúng là AJAX đã cho phép partial updates từ thời điểm đó. Nhưng nó chỉ là add-on cho những tương tác cụ thể, chứ không phải cách cốt lõi để xây dựng app. Mental model vẫn là "server render pages, AJAX chỉ là gia vị thêm vào.")
Đây là kiểu code mình viết hằng ngày — controllers lấy data và trả về HTML đầy đủ:
Mỗi tính năng mình xây dựng đều đi theo cùng một vòng lặp:
Mô hình này chạy ổn — và thật ra nó lại khá đơn giản và dễ hiểu. Mình không phải nghĩ đến client-side state, JavaScript bundling, hay hydration. Server là single source of truth. Nhưng sau nhiều dự án, mình nhận ra những pain points như vầy:
UpdatePanel, theo kiểu "partial postback" của ASP.NET Web Forms, nhưng nó khá nặng nề và không hề dễ xàiKỷ Nguyên jQuery — Thêm Interactivity Lên Server-Rendered Pages
Trước khi các SPA web apps bùng nổ, có một giai đoạn trung gian — và nếu bạn xây dựng web app vào cuối những năm 2000, bạn chắc chắn sẽ biết tới cách làm này: jQuery trên nền server-rendered pages.
Ý tưởng rất đơn giản: giữ server phụ trách render HTML, nhưng viết thêm jQuery code ở client side để UI trở nên interactive hơn. Cần dropdown menu? jQuery plugin. Muốn submit form mà không reload trang? $.ajax(). Cần ẩn/hiện một section? $('.panel').slideToggle(). Và thật lòng mà nói, hồi đó viết code kiểu này cảm thấy đã sướng hơn rất nhiều so với việc phải xài UpdatePanel của C#.
Đây là đoạn code trong một app ASP.NET MVC điển hình — server vẫn render trang, jQuery xử lý tương tác:
Cách làm này khá ổn để thêm tính tương tác vào từng trang riêng lẻ. Nhưng khi app ngày càng phức tạp hơn — real-time updates, form phức tạp, multi-step wizard — nhiều vấn đề bắt đầu xuất hiện:
jQuery không phải là vấn đề — nó làm tốt ở đúng thứ nó được thiết kế để làm: đơn giản hóa DOM manipulation trong thời kỳ browsers còn không nhất quán. Vấn đề là họ đang cố xây applications bằng một công cụ được thiết kế cho enhancements. Cộng đồng cần thứ gì đó khác hẳn — một cách để xây toàn bộ user interface trong browser, với real state management và một component model thực sự.
Và đó chính xác là điều các SPA frameworks thế hệ đầu tiên ra đời để giải quyết.
Làn sóng SPA đầu tiên (2010–2013)
Trước khi React tồn tại, nhiều framework đã tiên phong ý tưởng chuyển rendering từ server sang 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.
Những framework này chứng minh rằng SPA khả thi — trải nghiệm mượt mà, không reload, thứ mà server-rendered app không thể làm được. Nhưng tất cả đều có chung những vấn đề: state management phức tạp, DOM updates khó đoán, và performance xuống cấp khi app lớn dần. Two-way data binding dễ build demo nhỏ nhưng là cơn ác mộng khi debug app lớn ở scale lớn.
React xuất hiện với triết lý khác (2013)
Facebook đang gặp chính những vấn đề đó trong News Feed và Chat UI của họ. Tháng 5/2013, họ ra mắt React — không phải để cạnh tranh tính năng, mà là một hướng tiếp cận hoàn toàn khác cho cùng SPA rendering model:
Mental model rất đơn giản: state thay đổi → React re-render → chỉ phần DOM thay đổi được update. Không còn full-page reload, không còn server round-trip cho mỗi tương tác. React không phải SPA framework đầu tiên, nhưng nó giải quyết đúng vấn đề — và đó là lý do khiến React thành công cho tới tận ngày nay.
Khi SPA Thống Trị — Và Những Vấn Đề Nổi Lên
Sau khi React trỗi dậy, cả ngành công nghiệp đổ dồn vào client-side rendering. Quan điểm phổ biến lúc bấy giờ: "Server rendering đã chết. SPA là tương lai."
Nhưng khi SPA scale lên production, developer phát hiện ra những trade-off đáng kể:
| Vấn đề | Nguyên nhân |
|---|---|
| SEO challenges | Google có thể render JS, nhưng index nó theo dạng second wave chậm hơn — SSR content được index ngay lập tức, đáng tin cậy hơn, và hoạt động với mọi search engine lẫn social link preview |
| Slow initial load | Người dùng phải download và parse các JS bundle lớn trước khi thấy bất cứ thứ gì |
| Loading spinners | Data fetching bắt đầu sau khi app load xong — người dùng thấy spinner trước |
| Bundle bloat | Toàn bộ code được ship xuống browser, kể cả code người dùng có thể không bao giờ chạy đến |
| Waterfall requests | Component A load → fetch data → render Component B → fetch thêm data |
Nghe quen không? Server rendering đã giải quyết phần lớn những vấn đề này từ nhiều năm trước. Cộng đồng đơn giản là đã quên mất lý do tại sao.
Vậy... Quay lại server?
Khoảng từ năm 2016, hệ sinh thái React bắt đầu khám phá cách đưa server rendering trở lại — nhưng không phải kiểu cũ "server làm mọi thứ". Ngành công nghiệp đã học được một bài học quan trọng:
Mỗi vấn đề cộng đồng gặp phải dẫn đến một approach mới — và mỗi approach mang theo trade-offs riêng. Phần còn lại của tutorial này sẽ đi qua từng bước, pattern by pattern.
Tại sao tutorial này ra đời
Đây là điều mình rút ra được sau nhiều năm làm việc với React — bắt đầu từ một developer .NET C# MVC, rồi theo dõi React và hệ sinh thái JS qua nhiều lần thay đổi paradigm:
Mục tiêu của tutorial này rất đơn giản: hiểu từng rendering strategies đủ sâu để đưa ra lựa chọn phù hợp cho project cụ thể của bạn — không phải vì một tech influencer nói vậy, không phải vì nó đang trending, mà vì bạn hiểu trade-offs và có thể tự đưa ra quyết định của mình.
Hãy bắt đầu từ nơi mọi thứ bắt đầu với React: Client-Side Rendering.
Về tác giả
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!
// 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>