--- name: rendering-patterns description: Explains web rendering strategies including Client-Side Rendering (CSR), Server-Side Rendering (SSR), Static Site Generation (SSG), Incremental Static Regeneration (ISR), and streaming. Use when deciding rendering strategy, optimizing performance, or understanding when content is generated and sent to users. --- # Web Rendering Patterns ## Overview Rendering determines **when** and **where** HTML is generated. Each pattern has distinct performance, SEO, and infrastructure implications. ## Rendering Pattern Summary | Pattern | When Generated | Where Generated | Use Case | |---------|----------------|-----------------|----------| | CSR | Runtime (browser) | Client | Dashboards, authenticated apps | | SSR | Runtime (each request) | Server | Dynamic, personalized content | | SSG | Build time | Server/Build | Static content, blogs, docs | | ISR | Build + revalidation | Server | Content that changes periodically | | Streaming | Runtime (progressive) | Server | Long pages, slow data sources | ## Client-Side Rendering (CSR) **Browser generates HTML using JavaScript after page load.** ### Flow ``` 1. Browser requests page 2. Server returns minimal HTML shell + JS bundle 3. JS executes in browser 4. JS fetches data from API 5. JS renders HTML into DOM ``` ### Timeline ``` Request → Empty Shell → JS Downloads → JS Executes → Data Fetches → Content Visible [--------------- Time to Interactive (slow) ---------------] ``` ### Characteristics - **First Contentful Paint (FCP):** Slow (waiting for JS) - **Time to Interactive (TTI):** Slow (JS must execute + fetch data) - **SEO:** Poor (crawlers see empty shell unless they execute JS) - **Server load:** Low (static files only) - **Caching:** Easy (static assets) ### When to Use - Authenticated dashboards (no SEO needed) - Highly interactive apps - Real-time data that can't be pre-rendered - When server infrastructure is limited ### Code Pattern ```jsx // Pure CSR - data fetched in browser function ProductPage() { const [product, setProduct] = useState(null); useEffect(() => { fetch('/api/products/123') .then(res => res.json()) .then(setProduct); }, []); if (!product) return ; return ; } ``` ## Server-Side Rendering (SSR) **Server generates complete HTML for each request.** ### Flow ``` 1. Browser requests page 2. Server fetches data 3. Server renders HTML with data 4. Server sends complete HTML 5. Browser displays content immediately 6. JS hydrates for interactivity ``` ### Timeline ``` Request → Server Fetches Data → Server Renders → HTML Sent → Content Visible → Hydration → Interactive [--- Server Time ---] [--- Hydration ---] ``` ### Characteristics - **FCP:** Fast (complete HTML from server) - **TTI:** Depends on hydration time - **SEO:** Excellent (full HTML for crawlers) - **Server load:** High (render on every request) - **Caching:** Complex (varies by user/request) ### When to Use - SEO-critical pages with dynamic content - Personalized content (user-specific) - Frequently changing data - Pages that need fresh data on every request ### Code Pattern (Framework-agnostic concept) ```javascript // Server-side: runs on each request async function renderPage(request) { const data = await fetchData(request.params.id); const html = renderToString(); return html; } ``` ## Static Site Generation (SSG) **HTML generated once at build time.** ### Flow ``` Build Time: 1. Build process fetches all data 2. Generates HTML for all pages 3. Outputs static files Runtime: 1. Browser requests page 2. CDN serves pre-built HTML instantly ``` ### Timeline ``` Request → CDN Cache Hit → HTML Delivered → Content Visible (instant) ``` ### Characteristics - **FCP:** Fastest (pre-built, CDN-cached) - **TTI:** Fast (minimal JS, or none) - **SEO:** Excellent (complete HTML) - **Server load:** None at runtime (static files) - **Caching:** Trivial (immutable until next build) ### When to Use - Blogs, documentation, marketing pages - Content that changes infrequently - Pages where all possible URLs are known at build time - Maximum performance is required ### Limitations - Content stale until rebuild - Build time grows with page count - Can't handle dynamic routes unknown at build time - Personalization requires client-side hydration ## Incremental Static Regeneration (ISR) **SSG with automatic background regeneration.** ### Flow ``` 1. Initial build generates static pages 2. Pages served from cache 3. After revalidation time expires: - Serve stale page (fast) - Regenerate in background - Next request gets fresh page ``` ### Revalidation Strategies **Time-based:** ``` Page generated → Serve for 60 seconds → Regenerate on next request after 60s ``` **On-demand:** ``` CMS publishes → Webhook triggers regeneration → Page updated immediately ``` ### Characteristics - **FCP:** Fast (cached HTML) - **Freshness:** Configurable (seconds to hours) - **SEO:** Excellent - **Server load:** Low (regenerate occasionally) - **Scalability:** High (mostly static) ### When to Use - E-commerce product pages - News sites - Content that updates but not real-time - High-traffic pages that need freshness ## Streaming / Progressive Rendering **Server sends HTML in chunks as data becomes available.** ### Flow ``` 1. Browser requests page 2. Server immediately sends HTML shell 3. Server fetches data (possibly in parallel) 4. Server streams HTML chunks as ready 5. Browser renders progressively ``` ### Timeline ``` Request → Shell Sent → [Chunk 1 Streams] → [Chunk 2 Streams] → Complete [Visible] [More Visible] [Fully Visible] ``` ### Characteristics - **FCP:** Very fast (shell immediate) - **TTFB:** Fast (no waiting for all data) - **User Experience:** Progressive disclosure - **Complexity:** Higher implementation ### When to Use - Pages with multiple data sources - Slow API dependencies - Long pages where top should render first - Improving perceived performance ## Rendering Decision Flowchart ``` Does the page need SEO? ├── No → Does it need real-time data? │ ├── Yes → CSR │ └── No → SSG or CSR │ └── Yes → Is content the same for all users? ├── Yes → Does content change often? │ ├── Rarely → SSG │ ├── Sometimes → ISR │ └── Every request → SSR │ └── No (personalized) → SSR with caching strategies ``` ## Hybrid Approaches Modern apps mix patterns per route: | Route | Pattern | Reason | |-------|---------|--------| | `/` (home) | SSG | Static marketing content | | `/blog/*` | SSG/ISR | Content changes occasionally | | `/products/*` | ISR | Prices update, need SEO | | `/dashboard` | CSR | Authenticated, no SEO | | `/search` | SSR | Dynamic query results | ## Performance Metrics Impact | Pattern | TTFB | FCP | LCP | TTI | |---------|------|-----|-----|-----| | CSR | Fast | Slow | Slow | Slow | | SSR | Slower | Fast | Fast | Medium | | SSG | Fastest | Fastest | Fastest | Fast | | ISR | Fastest | Fastest | Fastest | Fast | | Streaming | Fast | Fast | Progressive | Medium | --- ## Deep Dive: Understanding How Rendering Actually Works ### What Does "Rendering" Actually Mean? Rendering is the process of converting your application code into HTML that a browser can display. Understanding this deeply requires knowing what happens at each layer. **The Rendering Pipeline:** ``` Your Code (JSX, Vue template, Svelte) ↓ Framework Virtual Representation (Virtual DOM, reactive graph) ↓ HTML String or DOM Operations ↓ Browser's DOM Tree ↓ Browser's Render Tree (DOM + CSS) ↓ Layout (position, size calculations) ↓ Paint (pixels on screen) ↓ Composite (layers combined) ``` When we say "server rendering" vs "client rendering", we're talking about WHERE the first few steps happen. ### The Fundamental Trade-off: Work Location Every web page requires work to be done. The question is: who does the work? ``` SERVER RENDERING: ┌─────────────────────────────────────────────────────────────────┐ │ SERVER (powerful, shared) │ │ │ │ [Fetch Data] → [Build HTML String] → [Send HTML] │ │ │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ CLIENT (varies, individual) │ │ │ │ [Parse HTML] → [Build DOM] → [Display] │ │ │ └─────────────────────────────────────────────────────────────────┘ CLIENT RENDERING: ┌─────────────────────────────────────────────────────────────────┐ │ SERVER (powerful, shared) │ │ │ │ [Send static JS bundle] │ │ │ └─────────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ CLIENT (varies, individual) │ │ │ │ [Download JS] → [Execute JS] → [Fetch Data] → [Build DOM] │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` Server rendering: Server does more work, client does less Client rendering: Server does less work, client does more ### How Server-Side Rendering Works Internally When a server renders HTML, it runs your component code to produce a string: ```javascript // What your React component looks like function ProductPage({ product }) { return (

{product.name}

${product.price}

); } // What the server actually does (simplified) function renderToString(component) { // 1. Execute component function const virtualDOM = component({ product: { name: 'Shoes', price: 99 } }); // 2. virtualDOM is a tree structure: // { // type: 'div', // props: { className: 'product' }, // children: [ // { type: 'h1', children: ['Shoes'] }, // { type: 'p', children: ['$99'] } // ] // } // 3. Convert tree to HTML string return '

Shoes

$99

'; } ``` The server runs JavaScript to produce HTML. It's executing your React/Vue/Svelte code, but instead of updating a browser DOM, it builds a string. ### How Client-Side Rendering Works Internally CSR works differently - it manipulates the live DOM: ```javascript // Browser receives empty HTML: //
// JavaScript bundle executes: const root = document.getElementById('root'); // Framework creates elements and appends them const div = document.createElement('div'); div.className = 'product'; const h1 = document.createElement('h1'); h1.textContent = 'Shoes'; const p = document.createElement('p'); p.textContent = '$99'; div.appendChild(h1); div.appendChild(p); root.appendChild(div); // Each DOM operation triggers browser work ``` Every DOM manipulation can trigger layout and paint. This is why virtual DOM exists - to batch changes. ### Why SSG is Fastest: CDN Edge Caching Static Site Generation's speed comes from CDN distribution: ``` Traditional SSR: User (Tokyo) → Request → Origin Server (New York) → Process → Response [───────────── 200-500ms ──────────────] Static + CDN: User (Tokyo) → Request → CDN Edge (Tokyo) → Cache Hit → Response [──────────── 20-50ms ────────────] ``` **How CDNs Work:** ``` First Request (cache miss): 1. User requests /products 2. CDN edge has no cache 3. Request goes to origin 4. Origin returns HTML 5. CDN caches it 6. User receives response Subsequent Requests (cache hit): 1. User requests /products 2. CDN edge has cached HTML 3. Immediate response (no origin contact) ``` With SSG, the HTML is pre-built and distributed to CDN edges worldwide BEFORE any user requests it. ### ISR: How Stale-While-Revalidate Works ISR combines caching with freshness. Understanding the timeline is key: ``` Build Time: Page generated, cached with revalidate: 60 Timeline: [0s] Page built, served to all users [30s] User A requests → Gets cached page (age: 30s, still fresh) [60s] Page is now "stale" but still cached [61s] User B requests → Gets stale page immediately Background: Server regenerates page [62s] New page ready, replaces old in cache [70s] User C requests → Gets fresh page (age: 8s) ``` The key insight: **the user who triggers revalidation gets the stale page**. The NEXT user gets fresh content. This is "stale-while-revalidate" strategy. ### Streaming: How HTTP Chunked Transfer Works Streaming isn't magic - it uses HTTP's chunked transfer encoding: ``` Normal Response: HTTP/1.1 200 OK Content-Length: 5000 [...waits until all 5000 bytes ready...] [...sends all at once...] Chunked/Streaming Response: HTTP/1.1 200 OK Transfer-Encoding: chunked 100 ← Chunk size in hex (256 bytes) ... ← Actual content 0 ← More chunks coming 200 ← Next chunk (512 bytes)
Content here...
0 0 ← Final chunk (0 means done) ``` The browser can start rendering BEFORE the full response arrives. **React Streaming Example:** ```jsx // Server Component async function Page() { return (
{/* Immediate - no data */} }> {/* Streams when ready */}