Hello World

Be Happy!

Frontend Rendering — RSC, SSR, SPA, Turbo


Overview

All approaches solve the same problem: show content to users and make it interactive. The difference is where rendering happens and how much JavaScript the browser needs.


1. Traditional Web (Server-Rendered HTML)

Sequence Diagram

Browser                          Server (Rails)
   |                                |
   |--- GET /article/123 ---------->|
   |                                | Renders ERB template
   |                                | Queries DB
   |                                | Builds full HTML
   |<-- HTML + small JS -----------|
   |                                |
   | Shows HTML immediately         |
   | Loads like-button.js (3 KB)    |
   |                                |
   | User clicks Like               |
   |--- POST /articles/123/like -->|
   |                                | Updates DB
   |<-- JSON { count: 43 } --------|
   | JS updates button text         |
   |                                |
  • Rendering: Server (Rails ERB, PHP, etc.)
  • JS sent: Only what each page needs (you link manually)
  • SEO: Perfect — HTML is ready
  • Navigation: Full page reload
  • Complexity: Lowest

2. Turbo (Turbolinks) — Rails

Sequence Diagram

Browser                          Server (Rails)
   |                                |
   |--- GET /article/123 ---------->|
   |                                | Renders ERB template
   |<-- Full HTML -----------------|
   | Shows page                     |
   |                                |
   | User clicks link to /article/456
   | (Turbo intercepts - NO full reload)
   |                                |
   |--- GET /article/456 ---------->|
   |                                | Renders ERB template
   |<-- HTML body only ------------|
   |                                |
   | Turbo swaps body              |
   | (smooth, no white flash)       |
   |                                |
   | User clicks Like               |
   |--- POST /articles/456/like -->|
   |                                | Updates DB
   |<-- Turbo Stream fragment -----|
   |    (small HTML piece)          |
   | Replaces like-count element    |
   |                                |
  • Rendering: Server (same ERB templates)
  • JS sent: ~20 KB (Turbo library) + page-specific JS
  • SEO: Perfect
  • Navigation: No full reload (body swap)
  • Complexity: Very low

3. SPA (Single Page Application) — Client-Side React

Sequence Diagram

Browser                          Server
   |                                |
   |--- GET / ---------------------->|
   |<-- Empty HTML + bundle.js ----| (200 KB JS)
   |                                |
   | Downloads bundle.js            |
   | Executes React                 |
   | React calls useEffect          |
   |                                |
   |--- GET /api/articles/123 ----->|
   |                                | Queries DB
   |<-- JSON { title, body } ------|
   |                                |
   | React renders article          |
   | (User finally sees content)    |
   |                                |
   | User clicks link to /article/456
   | (React Router - no reload)     |
   |                                |
   |--- GET /api/articles/456 ----->|
   |<-- JSON ----------------------|
   | React re-renders               |
   |                                |
  • Rendering: Browser (client-side)
  • JS sent: Everything (~200 KB+)
  • SEO: Bad — Google sees empty page until JS executes
  • Navigation: No reload (React Router)
  • Complexity: Medium

4. SSR (Server-Side Rendering) — Next.js

Sequence Diagram

Browser                          Server (Node.js)
   |                                |
   |--- GET /article/123 ---------->|
   |                                | Fetches data from API
   |                                | Runs React component
   |                                | Generates HTML string
   |<-- HTML + page.js (150 KB) ---|
   |                                |
   | Shows HTML immediately (fast!) |
   |                                |
   | Downloads page.js              |
   | React HYDRATES entire page:    |
   |   - Re-runs component          |
   |   - Rebuilds virtual DOM       |
   |   - Compares with real DOM     |
   |   - Attaches onClick to button |
   | (Duplicate work for h1, p...)  |
   |                                |
   | User clicks link to /article/456
   | (client-side navigation)       |
   |                                |
   |--- GET /article/456 chunk --->|
   |<-- page456.js (20 KB) --------|
   | React renders new page         |
   |                                |
  • Rendering: Server first, then browser re-processes (hydration)
  • JS sent: Full page as JS (~150 KB) — duplicates HTML content
  • SEO: Good — HTML is ready on first load
  • Navigation: No reload (client-side routing)
  • Complexity: Medium-High

5. RSC (React Server Components) — Next.js + React 19

Sequence Diagram

Browser                          Server (Node.js)
   |                                |
   |--- GET /article/123 ---------->|
   |                                | Runs Server Component
   |                                | Fetches data
   |                                | Renders h1, p, article (server only)
   |                                | Sees LikeButton is use client
   |                                | Serializes its props
   |<-- HTML + LikeButton.js (3KB)-|
   |                                |
   | Shows HTML immediately         |
   | Downloads LikeButton.js only   |
   | Hydrates ONLY the button       |
   | (h1, p = untouched, no JS)     |
   |                                |
   | User clicks link to /article/456
   | (client-side navigation)       |
   |                                |
   |--- GET /article/456?_rsc=1 -->|
   |                                | Server renders page 456
   |<-- RSC Payload (3 KB data) ---|
   |    (NOT JavaScript!)           |
   |                                |
   | React runtime reads payload    |
   | Patches DOM (no JS execution)  |
   | LikeButton already cached      |
   | (downloads nothing extra)      |
   |                                |
  • Rendering: Server for static parts, browser for interactive parts
  • JS sent: Only interactive components (~3-5 KB) + React runtime (~20 KB)
  • SEO: Perfect
  • Navigation: No reload (RSC payload patches DOM)
  • Complexity: High

Comparison

Traditional

  • Server renders HTML: Yes
  • SEO: Perfect
  • JS to browser: 0-5 KB
  • Page reload: Full
  • Hydration: None
  • Duplicate content: No
  • Complexity: Lowest

Turbo (Rails)

  • Server renders HTML: Yes
  • SEO: Perfect
  • JS to browser: ~20 KB
  • Page reload: No (body swap)
  • Hydration: None
  • Duplicate content: No
  • Complexity: Low

SPA

  • Server renders HTML: No
  • SEO: Bad
  • JS to browser: 200 KB+
  • Page reload: No
  • Hydration: N/A
  • Duplicate content: No
  • Complexity: Medium

SSR

  • Server renders HTML: Yes
  • SEO: Good
  • JS to browser: ~150 KB
  • Page reload: No
  • Hydration: Full page (wasteful)
  • Duplicate content: Yes (HTML + JS)
  • Complexity: Medium-High

RSC

  • Server renders HTML: Yes
  • SEO: Perfect
  • JS to browser: ~25 KB
  • Page reload: No
  • Hydration: Interactive parts only
  • Duplicate content: No
  • Complexity: High

The Evolution

Traditional Web (2000s) — Fast, simple, good SEO. But full page reloads feel clunky.

Turbo/AJAX (2010s) — No page reloads, still server-rendered. But complex interactivity is hard.

SPA (2013+) — Rich interactive UI. But bad SEO, slow first load, heavy JS.

SSR (2016+) — Fixes SEO, fast first paint. But duplicates content, hydrates wastefully.

RSC (2024+) — Minimal JS, selective hydration. But complex, and achieves what traditional web already did.


Business Logic Is Always Server-Side

Regardless of rendering approach, database operations always run on the server. User clicks Like → HTTP request → Server updates DB → Response → UI updates. This is identical in all approaches. Only the UI update part differs.

List