Claude Cursor GitHub Copilot Skill

high-perf-browser

Optimize web performance through network protocols, resource loading, and browser rendering internals. Use when the user mentions "my site is slow", "Core Web Vitals", "HTTP/2 or HTTP/3", "resource hints", "network latency", "render blocking", "TCP/TLS optimization", "service wor

LLM Mart · 0 points · 4 views 1 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download wondelai-skills-plugins_systems-architecture_skills_high-perf-browser-c172996.zip · 38 KB
Part of wondelai/skills — 183 skills

Install

skills CLI npx skills add https://github.com/wondelai/skills/tree/main/plugins/systems-architecture/skills/high-perf-browser
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install wondelai-skills@llmmart
Git git clone https://github.com/wondelai/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole wondelai/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

High Performance Browser Networking Framework

A systematic approach to web performance grounded in how browsers, protocols, and networks actually work. Apply these principles when building frontend applications, setting performance budgets, configuring servers, or diagnosing slow page loads.

Core Principle

Latency, not bandwidth, is the bottleneck. Most web performance problems stem from too many round trips, not too little throughput. A 5x bandwidth increase yields diminishing returns; a 5x latency reduction transforms the user experience.

The foundation: Every request passes through DNS resolution, TCP handshake, TLS negotiation, and HTTP exchange before a single byte of content arrives — each step adding round-trip latency. High-performance applications minimize round trips, parallelize requests, and eliminate unnecessary network hops. Understanding the protocol stack is the prerequisite for meaningful optimization.

Scoring

Goal: 10/10. Score by how many of the eight Quick Diagnostic rows pass, weighted toward the field metrics: 9-10 = all eight pass (the four field-metric rows in the green plus content-hashing, HTTP/2+, minimized render-blocking, and compression); 5-6 = the four field-metric rows pass but one or more transport/caching/compression rows fail; <=3 = any field-metric row is in the red. Always report the score, which diagnostic rows failed, and the specific fix for each.

The High Performance Browser Networking Framework

Six domains for building fast, resilient web applications:

1. Network Fundamentals

Core concept: Every HTTP request pays a latency tax — DNS lookup, TCP three-way handshake, TLS negotiation — before any application data flows. Reducing or eliminating these round trips is the single highest-leverage optimization.

Why it works: Light travels at a finite speed: a New York–London packet takes ~28ms one way regardless of bandwidth. These physics-level constraints cannot be solved with bigger pipes — only with fewer trips.

Key insights:

  • TCP three-way handshake adds one full RTT before data transfer begins
  • TCP slow start limits initial throughput to ~14KB (10 segments) in the first round trip — keep critical resources under this threshold
  • Upgrade to TLS 1.3: it halves the handshake round trips of TLS 1.2 and enables 0-RTT resumption for returning visitors
  • Head-of-line blocking in TCP means one lost packet stalls all streams on that connection
  • Bandwidth-delay product caps in-flight data; high-latency links underutilize bandwidth

Code applications:

Context Pattern Example
Connection warmup Pre-establish connections to critical origins <link rel="preconnect" href="https://cdn.example.com">
DNS prefetch Resolve third-party domains early (saves 20-120ms) <link rel="dns-prefetch" href="https://analytics.example.com">
TLS optimization TLS 1.3 + session resumption ssl_protocols TLSv1.3; with session tickets
Connection reuse Keep-alive avoids repeated handshakes Connection: keep-alive (default in HTTP/1.1+)

See references/network-fundamentals.md when tuning servers or diagnosing handshake latency — the full TLS 1.2-vs-1.3 RTT derivation, slow-start doubling table, initcwnd/BDP math, OCSP-stapling Nginx config, and the DNS cache hierarchy.

2. HTTP Protocol Evolution

Core concept: HTTP evolved from a simple request-response protocol into a multiplexed, binary system. Choosing the right protocol version and configuring it properly eliminates entire categories of performance problems.

Why it works: HTTP/1.1 forces workarounds (domain sharding, sprites, concatenation) because it cannot multiplex. HTTP/2 multiplexes but inherits TCP head-of-line blocking; HTTP/3 (QUIC over UDP) eliminates it. Each generation removes a bottleneck — and makes the previous generation's workarounds counterproductive.

Key insights:

  • HTTP/1.1 allows one outstanding request per TCP connection; browsers open 6 per host as a workaround
  • HTTP/2 multiplexes unlimited streams over one connection — domain sharding becomes counterproductive
  • HPACK header compression in HTTP/2 cuts repetitive header overhead by 85-95%
  • HTTP/3 (QUIC) eliminates TCP head-of-line blocking and enables 0-RTT resumption and connection migration
  • Prefer 103 Early Hints over HTTP/2 Server Push (which over-pushes and is widely deprecated)
  • Connection coalescing lets one HTTP/2 connection serve multiple hostnames sharing a certificate

Code applications:

Context Pattern Example
HTTP/2 migration Remove HTTP/1.1 workarounds Undo domain sharding, sprites, file concatenation
103 Early Hints Send preload hints before the full response 103 with Link: </style.css>; rel=preload
QUIC/HTTP/3 Advertise HTTP/3 on CDN or origin Alt-Svc: h3=":443" header
Stream prioritization Signal resource importance CSS and fonts highest priority; images lower

See references/http-protocols.md when picking or migrating a protocol version — side-by-side HTTP/1.1-vs-2-vs-3 comparison, the step-by-step de-sharding migration, and why Server Push lost to 103 Early Hints.

3. Resource Loading and Critical Rendering Path

Core concept: The browser must build the DOM, CSSOM, and render tree before painting pixels: HTML → DOM → CSSOM → Render Tree → Layout → Paint → Composite. Any resource that blocks this pipeline delays first paint.

Why it works: CSS is render-blocking (no paint until CSSOM is ready) while JavaScript is parser-blocking (<script> halts DOM construction until it downloads and executes) — so each needs a different optimization strategy. Every blocking resource adds latency directly to time-to-first-paint.

Key insights:

  • async downloads in parallel and executes immediately (use for independent scripts); defer downloads in parallel but executes after DOM parsing (use for most scripts)
  • <link rel="preload"> fetches critical resources at high priority now; rel="prefetch" fetches likely next-navigation resources at low priority
  • Inline above-the-fold CSS and async-load the rest to eliminate the render-blocking CSS request
  • Fonts can block text rendering for up to 3s — use font-display: swap

Code applications:

Context Pattern Example
Critical CSS Inline above-the-fold styles in <head> <style>/* critical */</style> + async full CSS
Script loading defer by default; async for independents <script src="app.js" defer></script>
Resource hints Preload critical fonts, hero images <link rel="preload" href="font.woff2" as="font" crossorigin>
Image optimization Lazy-load below-fold; modern formats <img loading="lazy" src="photo.avif" srcset="...">

See references/resource-loading.md when shaving first paint — the exact async/defer/module execution order, the full resource-hint decision tree, and the image/font (font-display, srcset, AVIF) playbook.

4. Caching Strategies

Core concept: The fastest network request is one that never happens. Layer caches — browser memory, disk, service worker, CDN, origin — to eliminate round trips for repeat visitors.

Why it works: Cache-Control headers tell the browser and intermediaries exactly how long a response stays valid; content-hashed URLs make aggressive immutable caching safe. Each cache hit eliminates a full network round trip.

Key insights:

  • Cache-Control: no-cache still caches but revalidates every time; no-store never caches — don't confuse them
  • ETag / Last-Modified enable conditional requests (304 Not Modified) that skip the body transfer
  • Service workers provide a programmable cache layer that works offline (cache-first shell, network-first dynamic content)
  • Misconfigured Vary headers cause CDN cache pollution — serve the wrong encoding or format to the wrong client

Code applications:

Context Pattern Example
Static assets Immutable cache + hash busting style.a1b2c3.css with Cache-Control: max-age=31536000, immutable
HTML documents Revalidate on every request Cache-Control: no-cache with ETag
API responses Short TTL + background refresh Cache-Control: max-age=60, stale-while-revalidate=3600
CDN config Cache at edge with correct Vary Vary: Accept-Encoding, Accept

See references/caching-strategies.md when designing a cache policy — the full browser/SW/CDN/origin hierarchy, copy-paste service-worker cache-first vs network-first recipes, and the Vary pitfalls that pollute a CDN.

5. Core Web Vitals Optimization

Core concept: Core Web Vitals — LCP, INP, CLS — are Google's user-centric metrics covering loading, interactivity, and visual stability. They impact search ranking and reflect real user experience.

Why it works: A fast TTFB means nothing if the hero image still loads late (LCP) or main-thread JavaScript blocks interactions (INP) — so server-side timing can look green while users wait. Optimize the perceived milestones, not the byte-delivery clock.

Key insights (numeric pass/fail thresholds live in the Quick Diagnostic):

  • LCP — optimize the largest visible element (hero image, heading block, video poster)
  • INP — keep the main thread free; break long tasks so every interaction (not only the first) stays responsive
  • CLS — reserve space for dynamic content before it loads
  • TTFB and FCP (< 1.8s) are upstream gates: they bound every downstream milestone, so fix them first
  • Measure with Real User Monitoring (RUM) in production — lab/synthetic tests miss real-device and network variance

Code applications:

Context Pattern Example
LCP Preload LCP element; raise its priority <img src="hero.webp" fetchpriority="high">
INP Break long tasks; yield to main thread scheduler.yield() or setTimeout chunking
CLS Reserve space for async content <img width="800" height="600"> or CSS aspect-ratio
Performance budget Fail CI when a vital regresses past its Quick Diagnostic threshold Lighthouse CI assertions on LCP/INP/CLS

See references/core-web-vitals.md when a metric is in the red — per-metric debugging workflows (what to inspect for a bad LCP/INP/CLS), the lab-vs-RUM tooling map, and per-vital optimization checklists.

6. Real-Time Communication

Core concept: When data must flow continuously, the transport choice — WebSocket, SSE, or long polling — determines latency, resource usage, and scalability.

Why it works: HTTP's request-response model adds overhead to every real-time update. WebSocket offers full-duplex with ~2-byte framing; SSE offers simpler server-to-client push over plain HTTP. Match the transport to the data flow direction and frequency instead of defaulting to the most powerful option.

Key insights:

  • WebSocket: bidirectional (chat, gaming, collaborative editing); SSE: server-to-client only, auto-reconnects, proxy-friendly, simpler
  • Long polling is a fallback only — high overhead from repeated HTTP requests
  • Each WebSocket is a separate TCP connection that bypasses HTTP/2 multiplexing
  • Send heartbeat/ping frames — mobile networks silently drop idle connections
  • Reconnect with exponential backoff and queue messages while disconnected

Code applications:

Context Pattern Example
Chat / collaboration WebSocket + heartbeat + reconnection new WebSocket('wss://...') with ping every 30s
Live feeds / notifications SSE for server-to-client streaming new EventSource('/api/updates')
Connection resilience Exponential backoff on reconnect 1s, 2s, 4s, 8s... capped at 30s
Scaling Pub/sub broker behind WebSocket servers Redis Pub/Sub or NATS

See references/real-time-communication.md when building a live feature — the WebSocket connect/heartbeat/reconnect lifecycle, the SSE EventSource pattern, and how to scale fan-out behind a pub/sub broker.

Common Mistakes

Mistake Why It Fails Fix
Adding bandwidth to fix slow pages Latency is the bottleneck, not throughput Reduce round trips: preconnect, cache, CDN
Loading all JS upfront Parser-blocking scripts delay paint and interactivity Code-split; defer; lazy-load non-critical modules
No resource hints Browser discovers critical resources too late preconnect + preload for above-fold criticals
Missing Cache-Control / no-store everywhere Every visit re-downloads everything Proper max-age + content hashing
Ignoring CLS Layout shifts destroy trust and ranking Explicit dimensions on images, embeds, ads
WebSocket for everything Needless complexity when SSE/polling suffices Match transport to data flow; SSE for server push
Domain sharding on HTTP/2 Defeats multiplexing; extra TCP connections Consolidate origins; let HTTP/2 multiplex
No compression Text resources transfer at full size Enable Brotli (preferred) or Gzip on server/CDN

Quick Diagnostic

Question If No Action
Is TTFB under 800ms? Server or network too slow CDN, server caching, check backend
Is LCP under 2.5s? Largest element loads too late Preload LCP resource; fetchpriority="high"
Is INP under 200ms? Main thread blocked Break long tasks; defer non-critical JS
Is CLS under 0.1? Elements shift after render Explicit dimensions; reserve space
Are static assets content-hashed and cached? Repeat visitors re-download Hashed filenames + Cache-Control: immutable
Is HTTP/2 or HTTP/3 enabled? No multiplexing or header compression Enable HTTP/2 on server; HTTP/3 via CDN
Are render-blocking resources minimized? CSS and sync JS delay first paint Inline critical CSS; defer scripts; prune unused CSS
Is compression enabled (Brotli/Gzip)? Uncompressed text transfers Enable Brotli on server/CDN; Gzip fallback

Further Reading

Based on Ilya Grigorik's comprehensive guide to browser networking and web performance:

About the Author

Ilya Grigorik is a web performance engineer who spent over a decade at Google working on Chrome, web platform performance, and HTTP standards, and co-chaired the W3C Web Performance Working Group. His book High Performance Browser Networking (O'Reilly, 2013) is widely regarded as the definitive reference on how browsers interact with the network.

Files (skills)
  • references
    • caching-strategies.md 11.7 KB
      # Caching Strategies
      
      The fastest network request is one that never happens. A well-designed caching strategy eliminates redundant data transfer, reduces server load, and dramatically improves load times for repeat visitors and subsequent navigations.
      
      
      ## Table of Contents
      1. [The Cache Hierarchy](#the-cache-hierarchy)
      2. [HTTP Cache-Control Headers](#http-cache-control-headers)
      3. [Conditional Requests and Revalidation](#conditional-requests-and-revalidation)
      4. [Content Hashing for Cache Busting](#content-hashing-for-cache-busting)
      5. [Service Workers for Cache Control](#service-workers-for-cache-control)
      6. [CDN Configuration](#cdn-configuration)
      7. [Stale-While-Revalidate](#stale-while-revalidate)
      8. [Caching Strategy by Resource Type](#caching-strategy-by-resource-type)
      9. [Common Caching Mistakes](#common-caching-mistakes)
      
      ---
      
      ## The Cache Hierarchy
      
      Browsers check caches in a specific order before making a network request:
      
      ```
      1. Memory cache (in-process, lost on tab close)
      2. Service worker cache (programmable, persistent)
      3. Disk cache (HTTP cache, persistent)
      4. CDN / edge cache (network, shared across users)
      5. Origin server (final fallback)
      ```
      
      Each layer closer to the user is faster. Memory cache is near-instant. Disk cache avoids the network entirely. CDN cache reduces RTT by serving from a nearby edge location. The goal is to satisfy as many requests as possible from the closest cache layer.
      
      ## HTTP Cache-Control Headers
      
      The `Cache-Control` header is the primary mechanism for controlling browser and CDN caching behavior.
      
      ### Essential directives
      
      | Directive | Meaning | Use case |
      |-----------|---------|----------|
      | `max-age=N` | Cache for N seconds without revalidation | Static assets with known freshness |
      | `no-cache` | Cache but always revalidate before use | HTML documents, API responses |
      | `no-store` | Do not cache at all | Sensitive data (banking, health) |
      | `immutable` | Never revalidate (even on reload) | Content-hashed static assets |
      | `public` | Can be cached by shared caches (CDN) | Public content |
      | `private` | Only browser can cache, not CDN | User-specific content |
      | `stale-while-revalidate=N` | Serve stale for N seconds while fetching fresh | Near-real-time content |
      | `stale-if-error=N` | Serve stale if origin returns an error | Fault tolerance |
      
      ### Common caching patterns
      
      **Static assets with content hashing (optimal):**
      ```
      Cache-Control: max-age=31536000, immutable
      ```
      Files like `app.a1b2c3.js` can be cached forever because the URL changes when content changes. `immutable` tells the browser to skip revalidation even when the user hits refresh.
      
      **HTML documents:**
      ```
      Cache-Control: no-cache
      ```
      The browser caches the document but revalidates on every request. Combined with `ETag`, this enables `304 Not Modified` responses that transfer only headers, not the full document.
      
      **API responses with near-real-time needs:**
      ```
      Cache-Control: max-age=0, stale-while-revalidate=60
      ```
      Always revalidate, but if the origin is slow, serve the cached response and update in the background.
      
      **Sensitive content:**
      ```
      Cache-Control: no-store
      ```
      Never cache. Use for authentication tokens, financial data, personal health information.
      
      **Shared public content:**
      ```
      Cache-Control: public, max-age=3600, stale-while-revalidate=86400
      ```
      CDN can cache for 1 hour; serve stale for up to 24 hours while refreshing.
      
      ## Conditional Requests and Revalidation
      
      When a cached resource expires (or uses `no-cache`), the browser sends a conditional request to check if the resource has changed.
      
      ### ETag (Entity Tag)
      
      The server generates a unique identifier (hash) for the response content:
      
      ```
      HTTP/1.1 200 OK
      ETag: "abc123def456"
      Cache-Control: no-cache
      ```
      
      On revalidation, the browser sends:
      ```
      GET /page.html HTTP/1.1
      If-None-Match: "abc123def456"
      ```
      
      If the content has not changed, the server responds:
      ```
      HTTP/1.1 304 Not Modified
      ```
      
      No body is transferred -- only headers. This saves bandwidth while ensuring freshness.
      
      ### Last-Modified
      
      A simpler mechanism using timestamps:
      
      ```
      HTTP/1.1 200 OK
      Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT
      ```
      
      Revalidation:
      ```
      GET /page.html HTTP/1.1
      If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT
      ```
      
      **ETag vs. Last-Modified:** ETag is more precise (content-based), while Last-Modified has second-level granularity. Servers should support both; browsers prefer ETag when both are present.
      
      ## Content Hashing for Cache Busting
      
      The most effective caching pattern combines long-lived cache headers with content-hashed filenames:
      
      ```
      styles.css → styles.a1b2c3.css
      app.js     → app.d4e5f6.js
      logo.png   → logo.g7h8i9.png
      ```
      
      **How it works:**
      1. Build tools generate a hash of each file's contents
      2. The hash is embedded in the filename
      3. HTML references the hashed filename
      4. The server sets `Cache-Control: max-age=31536000, immutable`
      5. When the file changes, a new hash produces a new URL
      6. The browser treats it as a completely new resource
      
      **Implementation with common build tools:**
      
      Webpack:
      ```javascript
      output: {
        filename: '[name].[contenthash].js',
        chunkFilename: '[name].[contenthash].js',
      }
      ```
      
      Vite:
      ```javascript
      build: {
        rollupOptions: {
          output: {
            entryFileNames: 'assets/[name].[hash].js',
            chunkFileNames: 'assets/[name].[hash].js',
            assetFileNames: 'assets/[name].[hash].[ext]',
          }
        }
      }
      ```
      
      **The HTML document itself cannot be hashed** (users navigate to `/index.html`, not `/index.a1b2c3.html`). This is why HTML uses `no-cache` while all referenced assets use content hashing.
      
      ## Service Workers for Cache Control
      
      Service workers provide a programmable cache layer between the browser and the network. They intercept every fetch request and can implement sophisticated caching strategies.
      
      ### Cache-first (offline-first)
      
      Serve from cache if available; fall back to network:
      
      ```javascript
      self.addEventListener('fetch', event => {
        event.respondWith(
          caches.match(event.request)
            .then(cached => cached || fetch(event.request))
        );
      });
      ```
      
      **Best for:** Static assets (CSS, JS, images, fonts) that rarely change.
      
      ### Network-first
      
      Try the network; fall back to cache if offline:
      
      ```javascript
      self.addEventListener('fetch', event => {
        event.respondWith(
          fetch(event.request)
            .then(response => {
              const clone = response.clone();
              caches.open('dynamic').then(cache => cache.put(event.request, clone));
              return response;
            })
            .catch(() => caches.match(event.request))
        );
      });
      ```
      
      **Best for:** HTML documents and API responses where freshness matters.
      
      ### Stale-while-revalidate
      
      Serve from cache immediately; update cache in the background:
      
      ```javascript
      self.addEventListener('fetch', event => {
        event.respondWith(
          caches.match(event.request).then(cached => {
            const fetchPromise = fetch(event.request).then(response => {
              const clone = response.clone();
              caches.open('dynamic').then(cache => cache.put(event.request, clone));
              return response;
            });
            return cached || fetchPromise;
          })
        );
      });
      ```
      
      **Best for:** Content that updates periodically but where instant display is preferred (news feeds, social timelines).
      
      ### Precaching the app shell
      
      During service worker installation, cache the core application shell:
      
      ```javascript
      const CACHE_NAME = 'app-shell-v1';
      const SHELL_URLS = [
        '/',
        '/styles.css',
        '/app.js',
        '/offline.html',
      ];
      
      self.addEventListener('install', event => {
        event.waitUntil(
          caches.open(CACHE_NAME)
            .then(cache => cache.addAll(SHELL_URLS))
        );
      });
      ```
      
      ### Cache versioning and cleanup
      
      Old caches must be cleaned up to prevent storage bloat:
      
      ```javascript
      self.addEventListener('activate', event => {
        event.waitUntil(
          caches.keys().then(keys =>
            Promise.all(
              keys
                .filter(key => key !== CACHE_NAME)
                .map(key => caches.delete(key))
            )
          )
        );
      });
      ```
      
      ## CDN Configuration
      
      CDNs cache content at edge locations close to users, reducing RTT for cached responses.
      
      ### CDN caching headers
      
      The `Cache-Control` header controls both browser and CDN caching. Use `s-maxage` to set a different TTL for shared caches (CDNs) vs. browsers:
      
      ```
      Cache-Control: public, max-age=60, s-maxage=3600
      ```
      
      This tells browsers to cache for 60 seconds but CDNs to cache for 1 hour.
      
      ### Vary header
      
      The `Vary` header tells caches which request headers affect the response:
      
      ```
      Vary: Accept-Encoding
      ```
      
      Without `Vary: Accept-Encoding`, a CDN might serve a Brotli-compressed response to a client that only supports Gzip.
      
      Common `Vary` values:
      - `Accept-Encoding` -- different compression (Brotli, Gzip, identity)
      - `Accept` -- different content types (HTML vs. JSON, AVIF vs. WebP)
      - `Accept-Language` -- different language versions
      
      **Warning:** `Vary: *` or `Vary: Cookie` effectively disables CDN caching because every request differs.
      
      ### Cache purging
      
      When content changes, CDN caches must be invalidated:
      
      - **Purge by URL:** Invalidate a specific resource
      - **Purge by tag:** Tag resources with categories; purge all resources with a tag
      - **Purge by prefix:** Invalidate all resources under a path
      - **Soft purge:** Mark as stale; serve stale while fetching fresh (similar to `stale-while-revalidate`)
      
      Content-hashed URLs largely eliminate the need for cache purging of static assets. Focus purging on HTML and API responses.
      
      ## Stale-While-Revalidate
      
      The `stale-while-revalidate` directive is one of the most powerful caching tools:
      
      ```
      Cache-Control: max-age=60, stale-while-revalidate=3600
      ```
      
      Behavior:
      1. **0-60 seconds:** Serve from cache without revalidation (fresh)
      2. **60-3660 seconds:** Serve from cache immediately (stale) AND fetch a fresh copy in the background
      3. **After 3660 seconds:** Cache is completely stale; must wait for network
      
      This pattern gives users instant responses while keeping content reasonably fresh. It is ideal for:
      - API endpoints that update periodically
      - Configuration data
      - Product listings
      - Any content where a few minutes of staleness is acceptable
      
      ## Caching Strategy by Resource Type
      
      | Resource | Cache-Control | Hash | Revalidation |
      |----------|--------------|------|-------------|
      | HTML | `no-cache` | No | ETag + 304 |
      | CSS (bundled) | `max-age=31536000, immutable` | Yes | None needed |
      | JavaScript (bundled) | `max-age=31536000, immutable` | Yes | None needed |
      | Images (static) | `max-age=31536000, immutable` | Yes | None needed |
      | Fonts | `max-age=31536000, immutable` | Yes | None needed |
      | API responses | `max-age=0, stale-while-revalidate=60` | No | ETag + 304 |
      | User-specific data | `private, no-cache` | No | ETag + 304 |
      | Sensitive data | `no-store` | No | N/A |
      
      ## Common Caching Mistakes
      
      | Mistake | Consequence | Fix |
      |---------|------------|-----|
      | No `Cache-Control` on static assets | Browser uses heuristic caching (unpredictable) | Explicitly set `max-age` and `immutable` |
      | `no-store` on everything | Every visit fetches all resources from origin | Use `no-cache` for HTML; long cache + hash for assets |
      | Missing `Vary: Accept-Encoding` | CDN serves wrong compression format | Add `Vary: Accept-Encoding` to compressed responses |
      | Cache busting with query strings | Some CDNs ignore query strings; proxies may not cache | Use filename hashing instead of `?v=123` |
      | No ETag on HTML | Conditional requests impossible; full document re-downloaded | Configure server to generate ETags |
      | `max-age=0` without `stale-while-revalidate` | Every request blocks on revalidation | Add `stale-while-revalidate` for better perceived performance |
      | Service worker caching everything | Cache grows unbounded; stale content persists | Implement cache limits and versioned cleanup |
      
      A comprehensive caching strategy is often the single highest-impact performance optimization -- it reduces server load, saves bandwidth, and makes repeat visits feel instant.
      
    • core-web-vitals.md 14.9 KB
      # Core Web Vitals Optimization
      
      Core Web Vitals are Google's standardized performance metrics that measure real user experience. They directly impact search ranking and correlate strongly with user engagement, conversion rates, and bounce rates.
      
      
      ## Table of Contents
      1. [The Three Core Web Vitals](#the-three-core-web-vitals)
      2. [LCP: Largest Contentful Paint](#lcp-largest-contentful-paint)
      3. [INP: Interaction to Next Paint](#inp-interaction-to-next-paint)
      4. [CLS: Cumulative Layout Shift](#cls-cumulative-layout-shift)
      5. [Measuring Core Web Vitals](#measuring-core-web-vitals)
      6. [Performance Budgets](#performance-budgets)
      7. [Debugging Workflow](#debugging-workflow)
      8. [Quick Reference: Optimization Impact](#quick-reference-optimization-impact)
      
      ---
      
      ## The Three Core Web Vitals
      
      | Metric | Measures | Good | Needs Improvement | Poor |
      |--------|----------|------|-------------------|------|
      | **LCP** (Largest Contentful Paint) | Loading performance | < 2.5s | 2.5s - 4.0s | > 4.0s |
      | **INP** (Interaction to Next Paint) | Interactivity | < 200ms | 200ms - 500ms | > 500ms |
      | **CLS** (Cumulative Layout Shift) | Visual stability | < 0.1 | 0.1 - 0.25 | > 0.25 |
      
      ### Supporting metrics
      
      | Metric | Measures | Target |
      |--------|----------|--------|
      | **TTFB** (Time to First Byte) | Server responsiveness | < 800ms |
      | **FCP** (First Contentful Paint) | Initial render speed | < 1.8s |
      | **TBT** (Total Blocking Time) | Main thread availability (lab proxy for INP) | < 200ms |
      
      ## LCP: Largest Contentful Paint
      
      LCP measures when the largest visible content element finishes rendering. This is what users perceive as "the page has loaded."
      
      ### What counts as the LCP element
      
      - `<img>` elements (including those inside `<picture>`)
      - `<video>` poster images
      - Elements with `background-image` via CSS
      - Block-level text elements (`<h1>`, `<p>`, etc.)
      
      The LCP element changes as the page loads. The final LCP element is the one that is largest when rendering stabilizes.
      
      ### Common LCP problems
      
      **Slow server response (high TTFB):** The browser cannot render anything until it receives the first byte of HTML.
      
      Fixes:
      - Add a CDN to reduce network latency
      - Implement server-side caching (Redis, Varnish)
      - Use streaming server-side rendering to send the HTML `<head>` immediately
      - Optimize database queries and backend processing
      - Use `103 Early Hints` to let the browser start fetching critical resources before the full response
      
      **Late-discovered LCP resource:** The browser discovers the LCP image late in the rendering process (e.g., a CSS background image or a `<img>` tag deep in the HTML).
      
      Fixes:
      ```html
      <!-- Preload the LCP image -->
      <link rel="preload" href="/hero.webp" as="image" fetchpriority="high">
      
      <!-- Or use fetchpriority directly -->
      <img src="/hero.webp" fetchpriority="high" alt="Hero" width="1200" height="600">
      ```
      
      **Render-blocking resources:** CSS and synchronous JavaScript delay rendering.
      
      Fixes:
      - Inline critical CSS in `<head>`
      - Defer non-critical CSS loading
      - Use `defer` or `async` on scripts
      - Remove unused CSS (audit with Coverage panel)
      
      **Slow resource load time:** The LCP image itself is too large or served from a slow origin.
      
      Fixes:
      - Compress images (AVIF, WebP)
      - Serve responsive images with `srcset`
      - Use a CDN for image delivery
      - Set appropriate cache headers
      
      **Client-side rendering:** SPAs that render content in JavaScript after the initial HTML load have inherently slow LCP.
      
      Fixes:
      - Use server-side rendering (SSR) or static site generation (SSG)
      - Stream HTML with `Transfer-Encoding: chunked`
      - Pre-render the above-fold content on the server
      
      ### LCP optimization checklist
      
      - [ ] TTFB under 800ms
      - [ ] LCP resource discoverable in HTML source (not CSS or JS)
      - [ ] LCP resource preloaded with `fetchpriority="high"`
      - [ ] No render-blocking resources before LCP
      - [ ] LCP image optimized (format, compression, responsive)
      - [ ] LCP image served from CDN
      - [ ] Critical CSS inlined; non-critical CSS deferred
      
      ## INP: Interaction to Next Paint
      
      INP measures the delay between a user interaction (click, tap, keypress) and the next visual update. It replaced FID (First Input Delay) as a Core Web Vital in March 2024.
      
      **Why INP matters more than FID:** FID only measured the delay of the first interaction. INP measures all interactions throughout the page lifecycle and reports the single worst one — except on pages with many interactions, where it drops roughly one outlier per 50 interactions (approximating the 98th percentile for high-interaction pages). A page can have a good FID but terrible INP if JavaScript blocks the main thread during later interactions.
      
      ### What causes poor INP
      
      **Long tasks on the main thread:** Any JavaScript task that runs for more than 50ms blocks the browser from processing user input.
      
      **Excessive event handler work:** Click handlers that perform heavy computation, DOM manipulation, or synchronous operations delay the visual response.
      
      **Layout thrashing:** Reading layout properties (like `offsetHeight`) and then writing to the DOM in a loop forces the browser to recalculate layout repeatedly.
      
      ### INP optimization strategies
      
      **Break up long tasks:**
      
      ```javascript
      // BAD: One long task blocking the main thread
      function processAllItems(items) {
        items.forEach(item => heavyOperation(item));
      }
      
      // GOOD: Yield to the main thread between chunks
      async function processAllItems(items) {
        for (const item of items) {
          heavyOperation(item);
          // Yield to let the browser handle pending interactions
          await scheduler.yield();
        }
      }
      ```
      
      If `scheduler.yield()` is not available, use a polyfill pattern:
      
      ```javascript
      function yieldToMain() {
        return new Promise(resolve => setTimeout(resolve, 0));
      }
      
      async function processAllItems(items) {
        for (let i = 0; i < items.length; i++) {
          heavyOperation(items[i]);
          if (i % 10 === 0) await yieldToMain();
        }
      }
      ```
      
      **Defer non-urgent work:**
      
      ```javascript
      // Use requestIdleCallback for work that does not need to happen immediately
      requestIdleCallback(() => {
        analytics.track('page_view');
        prefetchNextPage();
      });
      ```
      
      **Minimize event handler work:**
      
      ```javascript
      // BAD: Heavy computation in click handler
      button.addEventListener('click', () => {
        const result = expensiveCalculation();  // 200ms
        updateDOM(result);                       // 50ms
      });
      
      // GOOD: Show immediate feedback, defer heavy work
      button.addEventListener('click', () => {
        showLoadingState();                      // 5ms - immediate feedback
        requestAnimationFrame(() => {
          const result = expensiveCalculation();
          updateDOM(result);
          hideLoadingState();
        });
      });
      ```
      
      **Avoid layout thrashing:**
      
      ```javascript
      // BAD: Read-write-read-write forces repeated layout
      elements.forEach(el => {
        const height = el.offsetHeight;        // Read (forces layout)
        el.style.height = height * 2 + 'px';  // Write (invalidates layout)
      });
      
      // GOOD: Batch reads, then batch writes
      const heights = elements.map(el => el.offsetHeight);  // All reads
      elements.forEach((el, i) => {
        el.style.height = heights[i] * 2 + 'px';            // All writes
      });
      ```
      
      **Reduce JavaScript payload:**
      - Code-split: load only what the current page needs
      - Tree-shake: remove unused exports
      - Lazy-load non-critical modules
      - Defer third-party scripts (analytics, widgets)
      
      ### INP optimization checklist
      
      - [ ] No JavaScript tasks longer than 50ms on the main thread
      - [ ] Event handlers provide immediate visual feedback
      - [ ] Non-urgent work deferred with `requestIdleCallback` or `scheduler.yield()`
      - [ ] No layout thrashing (batched reads and writes)
      - [ ] JavaScript code-split and lazy-loaded
      - [ ] Third-party scripts loaded async or deferred
      
      ## CLS: Cumulative Layout Shift
      
      CLS measures how much visible content shifts unexpectedly during the page lifecycle. Layout shifts destroy user trust -- users click the wrong button, lose their reading position, or experience visual chaos.
      
      ### What causes layout shifts
      
      **Images without dimensions:** When an image loads, it pushes surrounding content down if no space was reserved.
      
      **Dynamic content injection:** Ads, banners, cookie notices, and lazy-loaded content that inserts above existing content.
      
      **Web fonts causing text reflow:** When a web font loads and replaces a fallback font with different metrics, text reflows and shifts surrounding elements.
      
      **Dynamic content resizing:** Accordions, tab panels, or carousels that change height.
      
      ### CLS prevention strategies
      
      **Always set image dimensions:**
      
      ```html
      <!-- Explicit dimensions reserve space -->
      <img src="photo.jpg" width="800" height="600" alt="Photo">
      
      <!-- CSS aspect-ratio works too -->
      <style>
        .hero-img { aspect-ratio: 16 / 9; width: 100%; }
      </style>
      ```
      
      **Reserve space for dynamic content:**
      
      ```css
      /* Reserve space for an ad slot */
      .ad-slot {
        min-height: 250px;
        background: #f0f0f0;
      }
      
      /* Reserve space for a cookie banner */
      .cookie-banner-placeholder {
        min-height: 80px;
      }
      ```
      
      **Use `font-display: optional` for strict CLS prevention:**
      
      ```css
      @font-face {
        font-family: 'Custom Font';
        src: url('font.woff2') format('woff2');
        font-display: optional;  /* Uses font only if already cached */
      }
      ```
      
      Alternatively, use `font-display: swap` with font metric overrides to match fallback metrics:
      
      ```css
      @font-face {
        font-family: 'Custom Font';
        src: url('font.woff2') format('woff2');
        font-display: swap;
        size-adjust: 105%;
        ascent-override: 95%;
        descent-override: 22%;
        line-gap-override: 0%;
      }
      ```
      
      **Insert dynamic content below the viewport or with explicit reservations:**
      
      ```javascript
      // BAD: Inserting a banner at the top pushes everything down
      document.body.prepend(bannerElement);
      
      // GOOD: Use CSS transforms that don't trigger layout
      // Or insert in a reserved slot with min-height already set
      document.querySelector('.banner-slot').appendChild(bannerElement);
      ```
      
      **Use CSS `contain` for independent layout regions:**
      
      ```css
      .widget {
        contain: layout;  /* Layout changes inside don't affect outside */
      }
      ```
      
      ### CLS optimization checklist
      
      - [ ] All images and videos have explicit width and height attributes
      - [ ] Ad slots and dynamic content areas have reserved minimum heights
      - [ ] Web fonts use `font-display: swap` or `optional` with metric overrides
      - [ ] No content inserted above existing visible content
      - [ ] CSS animations use `transform` and `opacity` only (composited properties)
      - [ ] Embeds (iframes, widgets) have explicit dimensions
      
      ## Measuring Core Web Vitals
      
      ### Lab tools (synthetic testing)
      
      | Tool | Metrics | Use case |
      |------|---------|----------|
      | **Lighthouse** (Chrome DevTools) | LCP, TBT, CLS | Development, auditing |
      | **WebPageTest** | All vitals + filmstrip | Detailed analysis |
      | **PageSpeed Insights** | Lab + field data | Quick overview |
      
      ### Field tools (real user monitoring)
      
      | Tool | Data source | Use case |
      |------|------------|----------|
      | **Chrome User Experience Report (CrUX)** | Chrome users | Public dataset, search ranking data |
      | **Google Search Console** | CrUX data per URL/group | SEO impact monitoring |
      | **web-vitals library** | Your users | Custom RUM implementation |
      
      ### Implementing RUM with the web-vitals library
      
      ```javascript
      import { onLCP, onINP, onCLS } from 'web-vitals';
      
      function sendToAnalytics(metric) {
        const body = JSON.stringify({
          name: metric.name,
          value: metric.value,
          rating: metric.rating,  // 'good', 'needs-improvement', 'poor'
          id: metric.id,
          navigationType: metric.navigationType,
        });
        // Use sendBeacon for reliable delivery
        navigator.sendBeacon('/analytics', body);
      }
      
      onLCP(sendToAnalytics);
      onINP(sendToAnalytics);
      onCLS(sendToAnalytics);
      ```
      
      ### Lab vs. field data
      
      | Aspect | Lab (synthetic) | Field (RUM) |
      |--------|----------------|-------------|
      | **Environment** | Controlled (specific device, network) | Real user conditions |
      | **INP measurement** | Uses TBT as proxy | True INP from real interactions |
      | **CLS measurement** | Page load only | Full page lifecycle |
      | **Reproducibility** | High | Low (varies by user) |
      | **Use for** | Debugging, development | Monitoring, search ranking |
      
      **Always prioritize field data** for understanding real performance. Lab data is useful for debugging but does not capture the diversity of real-world conditions.
      
      ## Performance Budgets
      
      Performance budgets set thresholds that prevent regressions:
      
      ```javascript
      // Example budget in a CI/CD pipeline
      const budgets = {
        lcp: 2500,      // ms
        inp: 200,       // ms (use TBT in lab)
        cls: 0.1,       // score
        ttfb: 800,      // ms
        totalJS: 300,   // KB (compressed)
        totalCSS: 50,   // KB (compressed)
        totalImages: 500, // KB
        totalFonts: 200,  // KB
      };
      ```
      
      ### Enforcing budgets
      
      **Lighthouse CI:** Run Lighthouse in CI and fail builds that exceed budgets:
      
      ```json
      {
        "ci": {
          "assert": {
            "assertions": {
              "categories:performance": ["error", { "minScore": 0.9 }],
              "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
              "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
              "total-blocking-time": ["error", { "maxNumericValue": 200 }]
            }
          }
        }
      }
      ```
      
      **Bundle size monitoring:** Tools like `bundlesize`, `size-limit`, or Webpack's `performance.maxAssetSize` can catch JavaScript bloat before it ships.
      
      ## Debugging Workflow
      
      When Core Web Vitals are poor, follow this systematic approach:
      
      ### 1. Identify the problem metric
      Check CrUX data in Search Console or PageSpeed Insights.
      
      ### 2. Reproduce in lab
      Use Lighthouse or WebPageTest with throttled conditions matching your users.
      
      ### 3. Diagnose root cause
      
      **For poor LCP:**
      - Check TTFB (server issue?)
      - Check resource waterfall (late-discovered LCP resource?)
      - Check render-blocking resources (too much blocking CSS/JS?)
      - Check resource size (unoptimized images?)
      
      **For poor INP:**
      - Record a Performance trace in DevTools
      - Identify long tasks (red flags in the flame chart)
      - Find the interaction that triggered the long task
      - Determine which script/function caused the blocking
      
      **For poor CLS:**
      - Use the Layout Shift Regions overlay in DevTools (Rendering panel)
      - Check for images without dimensions
      - Look for late-injected content (ads, banners)
      - Test font loading behavior
      
      ### 4. Fix and verify
      Implement the fix, measure in lab, deploy, and verify with field data (allow 28 days for CrUX data to reflect changes).
      
      ## Quick Reference: Optimization Impact
      
      | Optimization | LCP | INP | CLS | Effort |
      |-------------|-----|-----|-----|--------|
      | Add CDN | High | -- | -- | Low |
      | Preload LCP resource | High | -- | -- | Low |
      | Inline critical CSS | High | -- | -- | Medium |
      | Code-split JavaScript | Medium | High | -- | Medium |
      | Set image dimensions | -- | -- | High | Low |
      | Use `font-display: swap` | -- | -- | Medium | Low |
      | Defer third-party scripts | Medium | High | Medium | Low |
      | Implement streaming SSR | High | Medium | -- | High |
      | Break up long tasks | -- | High | -- | Medium |
      | Reserve ad slot space | -- | -- | High | Low |
      
      Optimizing Core Web Vitals is not a one-time task -- it requires continuous monitoring and a performance-aware development culture that treats metrics regressions as bugs.
      
    • http-protocols.md 12.3 KB
      # HTTP Protocol Evolution
      
      HTTP has evolved through three major generations, each solving critical performance limitations of its predecessor. Understanding the differences is essential for choosing the right optimization strategies.
      
      ## HTTP/1.1: The Workaround Era
      
      HTTP/1.1 (1997, RFC 2616; updated in 2014, RFC 7230-7235) introduced persistent connections and pipelining over the original HTTP/1.0 model, but its fundamental constraint remains: **one request-response pair at a time per TCP connection**.
      
      ### Key features
      
      **Keep-alive connections:** HTTP/1.0 opened a new TCP connection for every request. HTTP/1.1 defaults to persistent connections (`Connection: keep-alive`), reusing the same TCP connection for multiple sequential requests. This eliminates repeated TCP and TLS handshakes.
      
      **Pipelining (failed experiment):** HTTP/1.1 specified pipelining -- sending multiple requests without waiting for responses. In practice, pipelining was never widely adopted because:
      - Responses must arrive in the same order as requests (HOL blocking)
      - Many proxies and servers handled it incorrectly
      - A slow response blocks all subsequent responses
      - Browsers disabled it by default
      
      ### Performance workarounds for HTTP/1.1
      
      Because HTTP/1.1 cannot multiplex, developers invented workarounds:
      
      | Workaround | How It Works | Trade-off |
      |-----------|-------------|-----------|
      | **Domain sharding** | Split resources across 4-6 subdomains to open more parallel connections | More DNS lookups, TCP handshakes, and memory usage |
      | **File concatenation** | Bundle many JS/CSS files into one to reduce requests | Cache invalidation -- one change invalidates the entire bundle |
      | **CSS sprites** | Combine many small images into one spritesheet | Complex to maintain; unused pixels waste bandwidth |
      | **Inlining** | Embed small resources (CSS, images as data URIs) directly in HTML | Cannot be cached independently; increases HTML size |
      | **Cookie-free domains** | Serve static assets from a domain without cookies | Cookies sent on every request to the main domain add overhead |
      
      **These workarounds become anti-patterns in HTTP/2.** Domain sharding defeats multiplexing. Concatenation prevents granular caching. Sprites add unnecessary complexity.
      
      ### When HTTP/1.1 is still relevant
      
      - Legacy infrastructure that cannot upgrade
      - Environments where HTTP/2 proxying introduces bugs
      - Simple APIs with few concurrent requests
      - Very small sites where the overhead of HTTP/2 setup is not justified
      
      ## HTTP/2: Multiplexing and Binary Framing
      
      HTTP/2 (2015, RFC 7540; updated as RFC 9113) is a binary protocol that multiplexes many requests and responses over a single TCP connection.
      
      ### Core concepts
      
      **Binary framing layer:** HTTP/2 wraps HTTP semantics in a binary frame format. Headers and data are sent as separate frame types, enabling interleaving. This is invisible to application code -- the same HTTP methods, status codes, and headers are used.
      
      **Streams, messages, and frames:**
      - **Stream:** A bidirectional flow of frames within a connection, identified by an integer ID
      - **Message:** A complete HTTP request or response, composed of one or more frames
      - **Frame:** The smallest unit of communication (HEADERS frame, DATA frame, etc.)
      
      Multiple streams share one TCP connection, and frames from different streams can be interleaved. This eliminates the need for multiple connections.
      
      ### Key performance features
      
      **Multiplexing:** Unlimited concurrent streams over one TCP connection. A browser can request CSS, JS, images, and API data simultaneously without waiting for any single response to complete. This eliminates HTTP/1.1's HOL blocking at the application layer.
      
      **Header compression (HPACK):** HTTP headers are repetitive -- `User-Agent`, `Cookie`, `Accept-Encoding` are sent identically on every request. HPACK compresses headers using:
      - A static dictionary of common header name-value pairs
      - A dynamic dictionary that learns session-specific headers
      - Huffman encoding for values
      
      Real-world compression: **85-95% reduction** in header overhead. For API-heavy applications sending large cookies or authorization tokens on every request, this is significant.
      
      **Stream prioritization:** Clients can signal the relative importance of streams using dependency trees and weights. A well-configured priority scheme ensures:
      - CSS and fonts load before images
      - Above-fold resources load before below-fold
      - Critical API calls complete before prefetched data
      
      In practice, priority implementation varies across servers and CDNs. Test your specific infrastructure.
      
      **Server Push:** The server can proactively send resources the client has not yet requested. When a browser requests `index.html`, the server can push `style.css` and `app.js` alongside the HTML response.
      
      Caveats with Server Push:
      - The browser may already have the resource cached, wasting bandwidth
      - No reliable mechanism for the client to cancel a push it does not need
      - Complex to configure correctly
      - **Largely deprecated in practice** -- Chrome removed support in 2022
      - Use `103 Early Hints` instead: the server sends a `103` informational response with `Link` headers before the final response, allowing the browser to begin fetching hinted resources immediately
      
      ### Migration from HTTP/1.1 to HTTP/2
      
      **Remove anti-patterns:**
      1. **Remove domain sharding** -- consolidate resources onto one origin; multiple connections defeat multiplexing
      2. **Stop concatenating files** -- serve individual modules for granular caching
      3. **Eliminate sprites** -- individual images multiplex efficiently
      4. **Remove data URI inlining** -- let resources be cached independently
      
      **Add new optimizations:**
      1. **Prioritize critical resources** -- configure stream priorities or use `fetchpriority` attribute
      2. **Use `103 Early Hints`** -- hint critical resources before the server finishes processing
      3. **Reduce origin count** -- fewer origins means fewer TCP/TLS handshakes
      4. **Leverage connection coalescing** -- HTTP/2 can reuse a connection for multiple hostnames if they share a TLS certificate and resolve to the same IP
      
      ### HTTP/2 limitations
      
      **TCP head-of-line blocking:** Although HTTP/2 eliminates application-layer HOL blocking, it inherits TCP's transport-layer HOL blocking. A single lost TCP packet stalls all multiplexed streams until the packet is retransmitted. Under packet loss (mobile networks, congested links), HTTP/2 over one TCP connection can perform worse than HTTP/1.1 over six connections.
      
      This fundamental limitation motivated HTTP/3.
      
      ## HTTP/3 and QUIC: UDP-Based Transport
      
      HTTP/3 (2022, RFC 9114) runs over QUIC (RFC 9000), a transport protocol built on UDP. QUIC integrates transport and encryption into a single layer, eliminating the TCP+TLS stack.
      
      ### Why QUIC exists
      
      QUIC solves three problems that cannot be fixed in TCP:
      
      1. **Transport-layer HOL blocking:** QUIC provides independent streams. A lost packet on one stream does not block other streams. Each stream has its own loss recovery.
      
      2. **Connection establishment latency:** QUIC combines the transport handshake with the cryptographic handshake (always TLS 1.3) in a single round trip. For returning visitors, **0-RTT resumption** sends application data in the very first packet.
      
      3. **Connection migration:** TCP connections are identified by a 4-tuple (source IP, source port, destination IP, destination port). Changing networks (e.g., WiFi to cellular) breaks the connection. QUIC connections are identified by a connection ID, allowing seamless migration across network changes.
      
      ### QUIC architecture
      
      ```
      HTTP/3       (application semantics)
      QUIC         (transport: streams, flow control, loss recovery, encryption)
      UDP          (minimal transport: no handshake, no ordering, no reliability)
      IP           (network layer)
      ```
      
      Key differences from HTTP/2 over TCP+TLS:
      - **Always encrypted** -- QUIC mandates TLS 1.3; no cleartext mode
      - **Userspace implementation** -- runs in application space, not the OS kernel; faster iteration and deployment
      - **Improved loss recovery** -- more accurate RTT measurement, better loss detection
      - **Flow control** -- per-stream and connection-level, preventing one stream from starving others
      
      ### 0-RTT Connection Resumption
      
      For returning visitors, QUIC can send application data in the first packet using previously established cryptographic parameters:
      
      - First visit: 1-RTT handshake (same as TLS 1.3)
      - Subsequent visits: 0-RTT -- data flows immediately
      
      **Replay attack risk:** 0-RTT data can be replayed by an attacker. Only use 0-RTT for idempotent requests (GET, HEAD). Servers must implement replay protection for non-idempotent operations.
      
      ### Connection Migration
      
      When a mobile user switches from WiFi to cellular, TCP connections break because the source IP changes. The user experiences a full reconnection: new DNS lookup, new TCP handshake, new TLS handshake.
      
      QUIC connections survive network changes because they are identified by a connection ID rather than IP/port tuples. The connection seamlessly continues on the new network path with a path validation step.
      
      ### Deploying HTTP/3
      
      **Server support:** Enable QUIC on your server or CDN. Major CDNs (Cloudflare, Fastly, AWS CloudFront, Akamai) support HTTP/3.
      
      **Advertisement:** HTTP/3 is advertised via the `Alt-Svc` HTTP header:
      ```
      Alt-Svc: h3=":443"; ma=86400
      ```
      
      The browser first connects via HTTP/2 (TCP), receives the `Alt-Svc` header, and then migrates subsequent requests to HTTP/3 (QUIC). This provides a graceful upgrade path.
      
      **Firewall considerations:** QUIC uses UDP port 443. Some networks and firewalls block UDP traffic. Browsers fall back to HTTP/2 over TCP when QUIC is unavailable.
      
      ## Choosing the Right Protocol
      
      | Factor | HTTP/1.1 | HTTP/2 | HTTP/3 |
      |--------|---------|--------|--------|
      | **Multiplexing** | None (1 request per connection) | Full (unlimited streams over 1 TCP) | Full (unlimited streams over QUIC) |
      | **HOL blocking** | Application layer (per connection) | TCP layer (all streams blocked) | None (per-stream recovery) |
      | **Handshake latency** | TCP (1 RTT) + TLS (1-2 RTT) | Same as HTTP/1.1 | QUIC+TLS (1 RTT; 0-RTT resumption) |
      | **Header compression** | None | HPACK (static + dynamic tables) | QPACK (similar to HPACK, adapted for QUIC) |
      | **Connection migration** | No | No | Yes (connection ID based) |
      | **Encryption** | Optional | Practically required (browsers enforce) | Always (TLS 1.3 mandatory) |
      | **Best for** | Legacy; simple APIs | General web; most sites today | Mobile users; lossy networks; real-time |
      
      ### Migration strategy
      
      1. **Enable HTTP/2** on all web servers and CDNs (this should already be done)
      2. **Remove HTTP/1.1 workarounds** (domain sharding, concatenation, sprites)
      3. **Enable HTTP/3** on CDN first (lowest risk, highest mobile traffic benefit)
      4. **Advertise HTTP/3** via `Alt-Svc` headers
      5. **Monitor** connection protocol distribution and performance metrics per protocol
      6. **Test under packet loss** to validate HTTP/3 benefits for mobile users
      
      ## Protocol-Specific Optimization Checklist
      
      ### HTTP/2 optimizations
      - [ ] Single connection per origin (remove domain sharding)
      - [ ] Individual resource serving (stop concatenating)
      - [ ] Stream priority configuration on server
      - [ ] `103 Early Hints` for critical resources
      - [ ] Connection coalescing with wildcard certificates
      - [ ] HTTP/2 push removed (use Early Hints instead)
      
      ### HTTP/3 optimizations
      - [ ] QUIC enabled on CDN or origin
      - [ ] `Alt-Svc` header advertising HTTP/3
      - [ ] 0-RTT enabled for idempotent requests
      - [ ] UDP 443 allowed through firewalls
      - [ ] Fallback to HTTP/2 tested and verified
      - [ ] Connection migration tested on mobile
      
      ## Measuring Protocol Performance
      
      Use the Navigation Timing and Resource Timing APIs to measure protocol-level performance:
      
      ```javascript
      // Check protocol used
      const entries = performance.getEntriesByType('resource');
      entries.forEach(entry => {
        console.log(entry.name, entry.nextHopProtocol);
        // 'h2' for HTTP/2, 'h3' for HTTP/3
      });
      
      // Connection timing breakdown
      const nav = performance.getEntriesByType('navigation')[0];
      console.log('DNS:', nav.domainLookupEnd - nav.domainLookupStart);
      console.log('TCP:', nav.connectEnd - nav.connectStart);
      console.log('TLS:', nav.connectEnd - nav.secureConnectionStart);
      console.log('TTFB:', nav.responseStart - nav.requestStart);
      ```
      
      Understanding protocol capabilities and constraints is essential for choosing the right optimizations. An optimization that helps on HTTP/1.1 may be neutral or harmful on HTTP/2, and the reverse is also true.
      
    • network-fundamentals.md 10.7 KB
      # Network Fundamentals
      
      Understanding the transport layer is the foundation of web performance optimization. Every HTTP request traverses DNS, TCP, and TLS before application data flows -- and each layer imposes latency costs.
      
      ## Latency Is the Bottleneck
      
      The single most important insight in web performance: **latency, not bandwidth, is the constraining factor** for most web applications.
      
      Bandwidth has grown dramatically over the past decade. A typical broadband connection delivers 50-100+ Mbps. But latency -- the time for a packet to travel from client to server and back (round-trip time, or RTT) -- is constrained by physics. Light in fiber travels at roughly 200,000 km/s. A packet from New York to London (~5,500 km) takes at minimum ~28ms one way, ~56ms RTT. Real-world RTTs are higher due to routing, queuing, and processing delays.
      
      **Why this matters:** A typical web page requires dozens of network round trips during loading. If each round trip takes 50ms, 20 round trips add a full second of latency -- regardless of whether the connection is 10 Mbps or 100 Mbps. Reducing the number of round trips has a far greater impact than increasing bandwidth.
      
      ### The bandwidth-delay product
      
      The bandwidth-delay product (BDP) represents the maximum amount of data in flight at any given time:
      
      ```
      BDP = Bandwidth x RTT
      ```
      
      A 10 Mbps connection with 100ms RTT has a BDP of ~122KB. This means at most 122KB can be in transit at once. If the TCP window is smaller than the BDP, the connection underutilizes available bandwidth. High-latency links (satellite, intercontinental) have large BDPs and are especially sensitive to window sizing.
      
      ## TCP Three-Way Handshake
      
      Every new TCP connection begins with a three-way handshake:
      
      1. **SYN** -- Client sends a synchronize packet to the server
      2. **SYN-ACK** -- Server acknowledges and sends its own synchronize
      3. **ACK** -- Client acknowledges; data transfer can begin
      
      This handshake costs **one full RTT** before any application data flows. On a 100ms RTT connection, that is 100ms of pure overhead for every new TCP connection.
      
      ### Implications for web performance
      
      - Each new connection to a different origin pays this cost
      - HTTP/1.1 browsers open 6 connections per host -- 6 handshakes
      - HTTP/2 uses a single connection per origin, paying the handshake cost once
      - Connection reuse (`keep-alive`) amortizes the handshake cost across multiple requests
      
      ### TCP Fast Open (TFO)
      
      TCP Fast Open allows data to be sent in the SYN packet itself, eliminating one RTT on subsequent connections. The server generates a cookie on the first connection; the client includes this cookie in future SYN packets along with data:
      
      - First connection: normal three-way handshake (no savings)
      - Subsequent connections: data sent with SYN, saving one RTT
      - Server support required; not universally deployed
      - Works best for short, repeated connections to the same server
      
      ## Congestion Control
      
      TCP is a reliable, ordered, congestion-controlled protocol. It does not know the network capacity in advance, so it probes capacity through two mechanisms:
      
      ### Slow Start
      
      A new TCP connection begins by sending a small number of segments (typically 10, or ~14KB). For each acknowledged segment, the sender doubles its congestion window. This exponential growth continues until packet loss occurs or a threshold is reached.
      
      **The 14KB rule:** Because the initial congestion window is typically 10 segments (~14KB), the first round trip can deliver at most 14KB of data. This is why keeping the critical rendering payload (HTML + inline critical CSS) under 14KB is a high-leverage optimization -- it enables first paint in a single round trip after the TCP handshake.
      
      ```
      Round 1: Send 10 segments (~14KB)
      Round 2: Send 20 segments (~28KB)
      Round 3: Send 40 segments (~56KB)
      Round 4: Send 80 segments (~112KB)
      ...
      ```
      
      It takes several round trips to ramp up to full link capacity. On a high-latency connection, this ramp-up period significantly delays large transfers.
      
      ### Congestion Avoidance
      
      After slow start reaches a threshold (typically set by the first packet loss event), TCP switches to congestion avoidance: the window grows linearly (additive increase) rather than exponentially. On packet loss, the window is cut in half (multiplicative decrease). This AIMD (Additive Increase Multiplicative Decrease) algorithm is conservative by design.
      
      ### Modern congestion control algorithms
      
      - **Cubic** (Linux default): More aggressive window growth, optimized for high-BDP links
      - **BBR** (Bottleneck Bandwidth and Round-trip propagation time): Google's algorithm that models bandwidth and RTT explicitly rather than relying on packet loss as a signal. BBR can significantly improve throughput on high-latency, lossy links
      
      **Server-side optimization:** Increasing the initial congestion window (`initcwnd`) to 10 segments is now standard. Some operators increase it further. Enabling BBR on servers serving global traffic can improve throughput by 5-15%.
      
      ## Head-of-Line Blocking
      
      TCP guarantees ordered delivery. If packet 3 of 10 is lost, packets 4-10 are buffered at the receiver until packet 3 is retransmitted and arrives. This is **head-of-line (HOL) blocking**.
      
      ### Impact on HTTP
      
      - **HTTP/1.1:** One request at a time per connection. A slow response blocks subsequent requests on that connection. Browsers use 6 connections as a workaround.
      - **HTTP/2:** Multiple streams multiplexed over one TCP connection. A single lost TCP packet blocks ALL streams, not just the affected one. This is worse than HTTP/1.1's 6-connection model under packet loss.
      - **HTTP/3 (QUIC):** Runs over UDP with per-stream loss recovery. A lost packet on stream A does not block streams B, C, or D. This eliminates transport-layer HOL blocking entirely.
      
      HOL blocking is the primary technical motivation for HTTP/3's move to QUIC/UDP.
      
      ## TLS Handshake Optimization
      
      TLS (Transport Layer Security) encrypts data in transit. The handshake establishes cryptographic parameters and adds latency on top of the TCP handshake.
      
      ### TLS 1.2 Handshake
      
      The full TLS 1.2 handshake requires **2 additional round trips** after the TCP handshake:
      
      1. ClientHello / ServerHello (key exchange parameters)
      2. Certificate verification, key exchange completion
      3. Client sends Finished, server sends Finished
      
      Total for a new HTTPS connection: TCP handshake (1 RTT) + TLS 1.2 (2 RTTs) = **3 RTTs** before data flows.
      
      ### TLS 1.3 Handshake
      
      TLS 1.3 reduces the handshake to **1 round trip** by combining key exchange and parameter negotiation into a single message. It also supports **0-RTT resumption** for returning visitors:
      
      - **Full handshake:** 1 RTT (down from 2 in TLS 1.2)
      - **0-RTT resumption:** Application data sent in the first message, no handshake delay (with replay attack caveats)
      
      The savings are significant: on a 100ms RTT connection, TLS 1.3 saves 100-200ms per new connection compared to TLS 1.2.
      
      ### Session Resumption
      
      Both TLS 1.2 and 1.3 support session resumption, which allows returning clients to skip parts of the handshake:
      
      - **Session IDs** (TLS 1.2): Server stores session state; client presents the ID to resume
      - **Session Tickets** (TLS 1.2/1.3): Server encrypts session state into a ticket the client stores and presents later; server is stateless
      - **PSK (Pre-Shared Key)** (TLS 1.3): Enables 0-RTT data by using keys from a previous session
      
      ### OCSP Stapling
      
      Certificate validation normally requires the browser to contact the Certificate Authority's OCSP responder -- another DNS lookup and HTTP request. **OCSP stapling** lets the server attach (staple) a signed, time-stamped OCSP response to the TLS handshake, eliminating this extra round trip.
      
      Configuration (Nginx):
      ```nginx
      ssl_stapling on;
      ssl_stapling_verify on;
      ssl_trusted_certificate /path/to/chain.pem;
      ```
      
      ## DNS Resolution
      
      Before any TCP connection begins, the browser must resolve the hostname to an IP address via DNS. This process involves:
      
      1. Browser DNS cache (if the domain was recently resolved)
      2. OS DNS cache
      3. Router DNS cache
      4. ISP recursive resolver
      5. Authoritative nameserver (if not cached at any level)
      
      ### Latency impact
      
      A DNS lookup adds **20-120ms** depending on cache state and resolver distance. For third-party resources (analytics, CDNs, ad networks), the browser encounters new domains that may not be cached.
      
      ### Optimization strategies
      
      **dns-prefetch:** Resolve domains before the browser encounters requests to them:
      ```html
      <link rel="dns-prefetch" href="https://cdn.example.com">
      <link rel="dns-prefetch" href="https://api.example.com">
      ```
      
      **preconnect:** Goes further -- resolves DNS, completes TCP handshake, and negotiates TLS:
      ```html
      <link rel="preconnect" href="https://cdn.example.com">
      ```
      
      **Minimize third-party origins:** Each unique origin requires at least a DNS lookup. Consolidating resources onto fewer origins reduces DNS overhead.
      
      **TTL management:** Short DNS TTLs (under 60s) cause frequent re-resolution. For stable infrastructure, longer TTLs (300-3600s) reduce DNS overhead for repeat visitors.
      
      ## Practical Optimization Checklist
      
      | Optimization | Impact | Implementation |
      |-------------|--------|----------------|
      | Enable TLS 1.3 | Save 1-2 RTTs per new connection | Server TLS configuration |
      | Enable OCSP stapling | Eliminate 1 RTT for cert validation | Server TLS configuration |
      | Increase initial cwnd to 10+ | More data in first RTT | `ip route change ... initcwnd 10` (Linux) |
      | Enable TCP Fast Open | Save 1 RTT on repeat connections | Kernel parameter + server config |
      | Use `preconnect` for critical origins | Eliminate handshake latency at request time | `<link rel="preconnect">` in HTML |
      | Use `dns-prefetch` for third-party domains | Overlap DNS with other work | `<link rel="dns-prefetch">` in HTML |
      | Consolidate to fewer origins | Fewer handshakes overall | Migrate resources to primary domain or CDN |
      | Enable BBR congestion control | Better throughput on lossy/high-RTT links | `sysctl net.ipv4.tcp_congestion_control=bbr` |
      | Serve from edge locations (CDN) | Lower RTT to users | CDN for static assets and cacheable responses |
      | Monitor real-user latency | Identify bottlenecks users actually experience | Navigation Timing API, RUM tools |
      
      ## Key Metrics to Monitor
      
      - **RTT (Round-Trip Time):** The fundamental unit of latency. Measure with `navigator.connection.rtt` or server-side instrumentation.
      - **TTFB (Time to First Byte):** Includes DNS + TCP + TLS + server processing. Target < 800ms.
      - **Connection time:** TCP + TLS handshake duration. Visible in the Performance API's `connectStart` to `secureConnectionStart` to `connectEnd`.
      - **DNS time:** `domainLookupEnd - domainLookupStart` in the Resource Timing API.
      
      Understanding these fundamentals is not optional -- they determine the lower bound of page load performance regardless of how well-optimized the application code is.
      
    • real-time-communication.md 15.9 KB
      # Real-Time Communication
      
      When data must flow continuously between client and server, the standard HTTP request-response model introduces unnecessary overhead. Real-time communication protocols -- WebSocket, Server-Sent Events (SSE), and WebRTC -- each solve different use cases with different trade-offs.
      
      
      ## Table of Contents
      1. [Choosing the Right Approach](#choosing-the-right-approach)
      2. [WebSocket Protocol](#websocket-protocol)
      3. [Server-Sent Events (SSE)](#server-sent-events-sse)
      4. [Long Polling](#long-polling)
      5. [WebRTC Basics](#webrtc-basics)
      6. [Connection Management Best Practices](#connection-management-best-practices)
      7. [Scaling Real-Time Systems](#scaling-real-time-systems)
      
      ---
      
      ## Choosing the Right Approach
      
      | Requirement | Best transport | Why |
      |------------|---------------|-----|
      | Bidirectional, low-latency messaging | WebSocket | Full-duplex, minimal per-message overhead |
      | Server-to-client push (unidirectional) | SSE | Simpler API, auto-reconnect, works over HTTP |
      | Periodic data updates | HTTP polling or `stale-while-revalidate` | Simplest; no persistent connection needed |
      | Peer-to-peer audio/video/data | WebRTC | Direct peer connection, media-optimized |
      | Fallback for restricted networks | Long polling | Works everywhere HTTP works |
      
      **Default to the simplest option that meets requirements.** SSE handles many "real-time" use cases without WebSocket's complexity. HTTP polling with `stale-while-revalidate` is sufficient when updates happen every few seconds or minutes.
      
      ## WebSocket Protocol
      
      WebSocket provides a persistent, full-duplex communication channel over a single TCP connection. After an HTTP upgrade handshake, client and server exchange frames with minimal overhead (~2-6 bytes per frame).
      
      ### Connection lifecycle
      
      ```
      1. Client sends HTTP upgrade request
      2. Server responds with 101 Switching Protocols
      3. Connection upgraded to WebSocket (persistent, full-duplex)
      4. Client and server exchange frames freely
      5. Either side sends a close frame to terminate
      ```
      
      ### Opening handshake
      
      The WebSocket connection begins as an HTTP request:
      
      ```
      GET /ws HTTP/1.1
      Host: example.com
      Upgrade: websocket
      Connection: Upgrade
      Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
      Sec-WebSocket-Version: 13
      ```
      
      Server response:
      ```
      HTTP/1.1 101 Switching Protocols
      Upgrade: websocket
      Connection: Upgrade
      Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
      ```
      
      After this handshake, the connection is no longer HTTP -- it is a raw TCP connection with WebSocket framing.
      
      ### Client implementation
      
      ```javascript
      class WebSocketClient {
        constructor(url) {
          this.url = url;
          this.reconnectDelay = 1000;
          this.maxReconnectDelay = 30000;
          this.messageQueue = [];
          this.connect();
        }
      
        connect() {
          this.ws = new WebSocket(this.url);
      
          this.ws.onopen = () => {
            console.log('Connected');
            this.reconnectDelay = 1000;  // Reset backoff
            this.flushQueue();
          };
      
          this.ws.onmessage = (event) => {
            const data = JSON.parse(event.data);
            this.handleMessage(data);
          };
      
          this.ws.onclose = (event) => {
            if (!event.wasClean) {
              this.scheduleReconnect();
            }
          };
      
          this.ws.onerror = () => {
            // onerror is always followed by onclose
          };
        }
      
        send(data) {
          if (this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify(data));
          } else {
            this.messageQueue.push(data);
          }
        }
      
        flushQueue() {
          while (this.messageQueue.length > 0) {
            this.send(this.messageQueue.shift());
          }
        }
      
        scheduleReconnect() {
          setTimeout(() => this.connect(), this.reconnectDelay);
          this.reconnectDelay = Math.min(
            this.reconnectDelay * 2,
            this.maxReconnectDelay
          );
        }
      
        handleMessage(data) {
          // Application-specific message handling
        }
      }
      ```
      
      ### Frame types
      
      | Frame type | Opcode | Purpose |
      |-----------|--------|---------|
      | Text | 0x1 | UTF-8 text data |
      | Binary | 0x2 | Binary data (images, protobuf, etc.) |
      | Ping | 0x9 | Heartbeat request |
      | Pong | 0xA | Heartbeat response |
      | Close | 0x8 | Connection termination |
      
      ### Heartbeats and connection monitoring
      
      Mobile networks and load balancers silently drop idle connections. Heartbeats detect dead connections:
      
      ```javascript
      // Client-side heartbeat
      class HeartbeatWebSocket extends WebSocketClient {
        constructor(url, heartbeatInterval = 30000) {
          super(url);
          this.heartbeatInterval = heartbeatInterval;
          this.heartbeatTimer = null;
          this.pongReceived = true;
        }
      
        connect() {
          super.connect();
      
          this.ws.onopen = () => {
            this.startHeartbeat();
          };
        }
      
        startHeartbeat() {
          this.heartbeatTimer = setInterval(() => {
            if (!this.pongReceived) {
              // Server did not respond to last ping
              this.ws.close();
              return;
            }
            this.pongReceived = false;
            this.ws.send(JSON.stringify({ type: 'ping' }));
          }, this.heartbeatInterval);
        }
      
        handleMessage(data) {
          if (data.type === 'pong') {
            this.pongReceived = true;
            return;
          }
          // Handle other messages
        }
      }
      ```
      
      **Server-side heartbeat timing:**
      - 30 seconds is a common interval for most use cases
      - Mobile apps may use longer intervals (60-90s) to save battery
      - Trading and gaming may use shorter intervals (5-10s) for faster dead connection detection
      
      ### WebSocket and HTTP/2
      
      An important caveat: **WebSocket connections bypass HTTP/2 multiplexing.** Each WebSocket connection is a separate TCP connection, not a stream on an existing HTTP/2 connection. For applications that open many WebSocket connections to the same origin, this can create connection overhead.
      
      RFC 8441 defines WebSocket over HTTP/2, but browser support is limited.
      
      ### Binary vs. text frames
      
      For high-throughput applications, binary frames with a compact serialization format (Protocol Buffers, MessagePack, CBOR) significantly reduce message size:
      
      ```javascript
      // Text (JSON) - 84 bytes
      ws.send(JSON.stringify({ type: 'position', x: 123.456, y: 789.012, t: 1679000000 }));
      
      // Binary (Protocol Buffers) - ~20 bytes
      const buffer = Position.encode({ x: 123.456, y: 789.012, t: 1679000000 }).finish();
      ws.send(buffer);
      ```
      
      ## Server-Sent Events (SSE)
      
      SSE provides a simple, HTTP-based protocol for server-to-client push. The server holds an HTTP connection open and streams events to the client.
      
      ### Key advantages over WebSocket
      
      - **Simpler:** Works over standard HTTP; no upgrade handshake
      - **Auto-reconnect:** The `EventSource` API reconnects automatically with configurable retry delay
      - **Event IDs:** Built-in support for resuming from the last received event
      - **Works through HTTP proxies:** Standard HTTP, so no proxy configuration needed
      - **HTTP/2 compatible:** SSE connections are HTTP/2 streams (multiplexed with other requests)
      
      ### When SSE is sufficient
      
      SSE handles the majority of "real-time" web use cases:
      - Live notifications
      - Real-time dashboards and monitoring
      - Stock tickers and live scores
      - Chat (with a separate HTTP POST for sending messages)
      - Streaming AI responses (like ChatGPT)
      - Build/deployment status updates
      
      ### Server implementation
      
      The server sends a response with `Content-Type: text/event-stream`:
      
      ```
      HTTP/1.1 200 OK
      Content-Type: text/event-stream
      Cache-Control: no-cache
      Connection: keep-alive
      
      data: {"message": "Hello"}
      
      event: notification
      data: {"type": "alert", "text": "New message"}
      
      id: 42
      event: update
      data: {"price": 142.50}
      
      retry: 5000
      
      ```
      
      Event format:
      - `data:` -- the event payload (can span multiple lines)
      - `event:` -- event type (defaults to "message")
      - `id:` -- event ID (sent as `Last-Event-ID` on reconnect)
      - `retry:` -- reconnection delay in milliseconds
      
      ### Client implementation
      
      ```javascript
      const eventSource = new EventSource('/api/stream');
      
      // Default "message" events
      eventSource.onmessage = (event) => {
        const data = JSON.parse(event.data);
        console.log('Message:', data);
      };
      
      // Named events
      eventSource.addEventListener('notification', (event) => {
        const data = JSON.parse(event.data);
        showNotification(data);
      });
      
      // Connection management
      eventSource.onerror = (event) => {
        if (eventSource.readyState === EventSource.CLOSED) {
          console.log('Connection closed by server');
        } else {
          console.log('Connection error, will auto-reconnect');
        }
      };
      
      // Close when done
      eventSource.close();
      ```
      
      ### Resuming after disconnection
      
      When the connection drops, the browser sends the last received event ID:
      
      ```
      GET /api/stream HTTP/1.1
      Last-Event-ID: 42
      ```
      
      The server can use this to resume from where the client left off, preventing data loss during brief disconnections.
      
      ### SSE with authentication
      
      `EventSource` does not support custom headers. Workarounds:
      
      ```javascript
      // Option 1: Token in URL (less secure, logged in server access logs)
      const eventSource = new EventSource('/api/stream?token=abc123');
      
      // Option 2: Cookie-based authentication (preferred)
      // Set an HttpOnly cookie first; EventSource sends cookies automatically
      
      // Option 3: Use fetch() with ReadableStream for custom headers
      async function streamWithHeaders(url, headers) {
        const response = await fetch(url, { headers });
        const reader = response.body.getReader();
        const decoder = new TextDecoder();
      
        while (true) {
          const { done, value } = await reader.read();
          if (done) break;
          const text = decoder.decode(value);
          // Parse SSE format manually
          processSSEText(text);
        }
      }
      ```
      
      ## Long Polling
      
      Long polling is the fallback when WebSocket and SSE are unavailable (restrictive firewalls, legacy infrastructure).
      
      ### How it works
      
      1. Client sends an HTTP request
      2. Server holds the request open until it has new data (or a timeout occurs)
      3. Server sends the response
      4. Client immediately sends a new request
      5. Repeat
      
      ```javascript
      async function longPoll(url, lastEventId = null) {
        while (true) {
          try {
            const params = lastEventId ? `?since=${lastEventId}` : '';
            const response = await fetch(`${url}${params}`, {
              signal: AbortSignal.timeout(60000),  // 60s timeout
            });
      
            if (response.ok) {
              const data = await response.json();
              lastEventId = data.id;
              handleUpdate(data);
            }
          } catch (error) {
            if (error.name === 'TimeoutError') {
              // Normal timeout, reconnect immediately
              continue;
            }
            // Network error, back off
            await new Promise(r => setTimeout(r, 5000));
          }
        }
      }
      ```
      
      ### Long polling trade-offs
      
      | Aspect | Long polling | WebSocket | SSE |
      |--------|-------------|-----------|-----|
      | Latency | Higher (new request each time) | Lowest | Low |
      | Server resources | High (many open connections) | Medium | Medium |
      | Complexity | Low | Medium | Low |
      | Firewall compatibility | Highest | Lower | High |
      | Bidirectional | Yes (via separate requests) | Yes (native) | No (one-way) |
      
      ## WebRTC Basics
      
      WebRTC enables peer-to-peer communication for audio, video, and arbitrary data between browsers without a relay server.
      
      ### Architecture
      
      ```
      Browser A ←→ Signaling Server ←→ Browser B
           ↕                                ↕
           └────── Direct P2P Connection ──┘
      ```
      
      1. **Signaling:** Peers exchange session descriptions (SDP) and ICE candidates through a signaling server (WebSocket, HTTP, or any mechanism)
      2. **ICE:** Interactive Connectivity Establishment discovers the best network path (direct, STUN, or TURN relay)
      3. **DTLS/SRTP:** Encrypted media and data channels over UDP
      
      ### Data channels
      
      For non-media real-time data (gaming, file transfer, screen sharing metadata), WebRTC data channels provide:
      - Ordered or unordered delivery
      - Reliable or unreliable (lossy) transport
      - Low latency (UDP-based)
      - Direct peer-to-peer (no server relay for media)
      
      ```javascript
      const peerConnection = new RTCPeerConnection({
        iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
      });
      
      const dataChannel = peerConnection.createDataChannel('game', {
        ordered: false,        // Unordered for lower latency
        maxRetransmits: 0,     // Unreliable (no retransmission)
      });
      
      dataChannel.onopen = () => {
        dataChannel.send(JSON.stringify({ type: 'move', x: 10, y: 20 }));
      };
      ```
      
      ### When to use WebRTC
      
      - Video/audio calls
      - Screen sharing
      - Peer-to-peer file transfer
      - Low-latency gaming (data channels)
      - IoT device streaming
      
      **Do not use WebRTC for:** Standard server-to-client push (use SSE or WebSocket), REST API alternatives, or scenarios where a server relay is needed anyway.
      
      ## Connection Management Best Practices
      
      ### Exponential backoff with jitter
      
      When reconnecting after a failure, exponential backoff prevents thundering herd problems:
      
      ```javascript
      function getReconnectDelay(attempt, baseDelay = 1000, maxDelay = 30000) {
        const exponentialDelay = baseDelay * Math.pow(2, attempt);
        const cappedDelay = Math.min(exponentialDelay, maxDelay);
        // Add random jitter (0-100% of delay) to prevent synchronized reconnects
        const jitter = cappedDelay * Math.random();
        return cappedDelay + jitter;
      }
      ```
      
      Without jitter, if a server restarts and 10,000 clients all reconnect simultaneously with the same backoff timing, the server faces a thundering herd of reconnection attempts.
      
      ### Message queuing during disconnection
      
      Queue outbound messages when disconnected and flush when reconnected:
      
      ```javascript
      class ResilientConnection {
        constructor() {
          this.queue = [];
          this.connected = false;
        }
      
        send(message) {
          if (this.connected) {
            this.transport.send(message);
          } else {
            this.queue.push(message);
            if (this.queue.length > 1000) {
              this.queue.shift();  // Prevent unbounded growth
            }
          }
        }
      
        onReconnect() {
          this.connected = true;
          while (this.queue.length > 0) {
            this.transport.send(this.queue.shift());
          }
        }
      }
      ```
      
      ### Connection lifecycle on mobile
      
      Mobile connections face unique challenges:
      - **Network switches:** WiFi to cellular transitions drop TCP connections
      - **Background tabs:** Browsers throttle or suspend timers and connections in background tabs
      - **Battery optimization:** OS may terminate background network activity
      
      Strategies:
      - Detect visibility changes with `document.visibilitychange` and reconnect when returning to foreground
      - Use shorter heartbeat intervals on mobile to detect dead connections faster
      - Implement message ID tracking so the server can resume from the last acknowledged message
      
      ```javascript
      document.addEventListener('visibilitychange', () => {
        if (document.visibilityState === 'visible') {
          // Tab became visible -- check connection health
          if (!isConnectionAlive()) {
            reconnect();
          }
        }
      });
      ```
      
      ## Scaling Real-Time Systems
      
      ### Horizontal scaling with pub/sub
      
      WebSocket connections are stateful -- a client connects to a specific server instance. To scale horizontally, use a pub/sub broker:
      
      ```
      Client A → Server 1 ←→ Redis Pub/Sub ←→ Server 2 → Client B
      Client C → Server 1 ←→     or NATS     ←→ Server 3 → Client D
      ```
      
      When Client A sends a message, Server 1 publishes it to Redis. All servers subscribed to that channel receive it and forward to their connected clients.
      
      ### Connection limits
      
      Each WebSocket connection consumes:
      - A file descriptor on the server
      - Memory for the connection state
      - CPU for message processing
      
      Practical limits:
      - A single Node.js process can handle ~50,000-100,000 concurrent WebSocket connections (depending on message rate)
      - Use connection limits and load balancing to distribute connections
      - Consider connection pooling for services that broadcast to many clients
      
      ### Load balancing considerations
      
      - **Sticky sessions:** Required for WebSocket and long polling (the same client must reach the same server)
      - **Connection draining:** When scaling down, drain connections gracefully before terminating a server instance
      - **Health checks:** Exclude servers with too many connections or high latency from the load balancer pool
      
      The choice of real-time transport should be driven by requirements, not technology preferences. SSE handles most server-push use cases with far less complexity than WebSocket. WebSocket is essential for bidirectional, low-latency communication. WebRTC is specialized for peer-to-peer media. Long polling is the universal fallback.
      
    • resource-loading.md 13.6 KB
      # Resource Loading and Critical Rendering Path
      
      The browser converts raw bytes into rendered pixels through a precise pipeline. Understanding this pipeline reveals exactly which resources block rendering and how to eliminate or defer them.
      
      
      ## Table of Contents
      1. [The Critical Rendering Path](#the-critical-rendering-path)
      2. [Render-Blocking Resources](#render-blocking-resources)
      3. [Async and Defer](#async-and-defer)
      4. [Resource Hints](#resource-hints)
      5. [Font Loading Strategies](#font-loading-strategies)
      6. [Image Optimization](#image-optimization)
      7. [Resource Loading Priority](#resource-loading-priority)
      8. [Practical Loading Strategy](#practical-loading-strategy)
      
      ---
      
      ## The Critical Rendering Path
      
      Every page load follows this sequence:
      
      ```
      HTML bytes → Parse → DOM
                            ↓
      CSS bytes  → Parse → CSSOM
                            ↓
                    Render Tree (DOM + CSSOM)
                            ↓
                          Layout (geometry calculation)
                            ↓
                          Paint (pixel rendering)
                            ↓
                          Composite (layer assembly)
      ```
      
      **The key insight:** The browser cannot paint anything until both the DOM and CSSOM are complete. CSS is **render-blocking**. JavaScript is **parser-blocking**. Every blocking resource directly adds to time-to-first-paint.
      
      ### DOM Construction
      
      The browser receives HTML bytes, decodes them into characters, tokenizes them into tags, and builds the DOM tree. This is incremental -- the parser processes HTML as it arrives, building the tree progressively.
      
      **When parsing stops:**
      - A `<script>` tag without `async` or `defer` halts DOM construction. The browser must download the script, execute it (because it might call `document.write()`), and then resume parsing.
      - The parser does not stop for CSS files, but rendering is blocked until CSS is parsed.
      
      ### CSSOM Construction
      
      CSS files are fetched and parsed into the CSSOM (CSS Object Model). Unlike DOM construction:
      - CSSOM is not incremental -- the browser waits for all CSS before computing styles
      - CSS is render-blocking but not parser-blocking (DOM construction continues)
      - A `<script>` after a `<link rel="stylesheet">` is blocked until the CSS loads (because the script might query computed styles)
      
      ### Render Tree
      
      The render tree combines the DOM and CSSOM, containing only visible elements with their computed styles. Elements with `display: none` are excluded. The render tree triggers:
      
      1. **Layout (reflow):** Calculates the exact position and size of each element
      2. **Paint:** Fills in pixels -- text, colors, borders, shadows, images
      3. **Composite:** Assembles painted layers into the final image (GPU-accelerated for transformed/animated elements)
      
      ## Render-Blocking Resources
      
      ### CSS is render-blocking
      
      The browser will not render any content until all CSS in the `<head>` is loaded and parsed. This means:
      
      ```html
      <!-- This CSS file blocks ALL rendering until loaded -->
      <link rel="stylesheet" href="styles.css">
      ```
      
      **Optimization strategies:**
      
      **Inline critical CSS:** Extract the styles needed for above-the-fold content and embed them directly in the HTML:
      
      ```html
      <head>
        <style>
          /* Critical styles for above-the-fold content */
          body { font-family: system-ui; margin: 0; }
          .hero { padding: 2rem; background: #f8f9fa; }
          .nav { display: flex; gap: 1rem; }
        </style>
        <!-- Load full stylesheet asynchronously -->
        <link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
        <noscript><link rel="stylesheet" href="styles.css"></noscript>
      </head>
      ```
      
      **Media queries for conditional CSS:** Stylesheets with media queries that do not match the current context are downloaded but do not block rendering:
      
      ```html
      <!-- Blocks rendering (matches all screens) -->
      <link rel="stylesheet" href="styles.css">
      
      <!-- Does NOT block rendering on screen (only for print) -->
      <link rel="stylesheet" href="print.css" media="print">
      
      <!-- Does NOT block rendering below 768px -->
      <link rel="stylesheet" href="desktop.css" media="(min-width: 768px)">
      ```
      
      **Remove unused CSS:** Audit CSS with Chrome DevTools Coverage panel. Typical sites ship 60-90% unused CSS.
      
      ### JavaScript is parser-blocking
      
      A `<script>` tag without attributes halts DOM parsing:
      
      ```html
      <p>This paragraph is parsed</p>
      <script src="app.js"></script>
      <!-- DOM parsing stops until app.js downloads AND executes -->
      <p>This paragraph waits</p>
      ```
      
      ## Async and Defer
      
      The `async` and `defer` attributes change how scripts interact with the parser:
      
      ### `defer`
      
      ```html
      <script src="app.js" defer></script>
      ```
      
      - Downloads in parallel with DOM parsing (non-blocking)
      - Executes **after** DOM parsing is complete, before `DOMContentLoaded`
      - Multiple deferred scripts execute in document order
      - **Best for:** Most application scripts that depend on the DOM
      
      ### `async`
      
      ```html
      <script src="analytics.js" async></script>
      ```
      
      - Downloads in parallel with DOM parsing (non-blocking)
      - Executes **immediately** when download completes (may interrupt parsing)
      - No guaranteed execution order between multiple async scripts
      - **Best for:** Independent scripts that do not depend on DOM or other scripts (analytics, ads)
      
      ### Comparison
      
      | Attribute | Download | Execution | Order guaranteed | Blocks parsing |
      |-----------|----------|-----------|-----------------|----------------|
      | None | Sequential | Immediately | Yes | Yes |
      | `async` | Parallel | When ready | No | Briefly (during execution) |
      | `defer` | Parallel | After DOM parsed | Yes | No |
      
      ### Module scripts
      
      ```html
      <script type="module" src="app.mjs"></script>
      ```
      
      Module scripts are deferred by default. Adding `async` to a module script makes it execute as soon as its dependency graph is resolved.
      
      ## Resource Hints
      
      Resource hints allow developers to inform the browser about resources it will need, enabling earlier loading.
      
      ### `dns-prefetch`
      
      Resolves the DNS for a domain before any request is made:
      
      ```html
      <link rel="dns-prefetch" href="https://cdn.example.com">
      ```
      
      - Cheapest hint -- only does DNS resolution
      - Use for third-party domains (analytics, fonts, CDNs)
      - Saves 20-120ms per domain
      
      ### `preconnect`
      
      Resolves DNS, establishes TCP connection, and negotiates TLS:
      
      ```html
      <link rel="preconnect" href="https://cdn.example.com">
      ```
      
      - More expensive than `dns-prefetch` (holds a connection open)
      - Use for origins you will definitely request from within seconds
      - Limit to 2-4 critical origins to avoid wasting connections
      - Always include `crossorigin` for CORS-enabled resources:
      
      ```html
      <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
      ```
      
      ### `preload`
      
      Fetches a specific resource at high priority without blocking rendering:
      
      ```html
      <link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
      <link rel="preload" href="/hero.webp" as="image">
      <link rel="preload" href="/critical.css" as="style">
      ```
      
      - **The `as` attribute is required** -- it sets the correct priority and CORS mode
      - Fetches immediately at high priority
      - Does not apply the resource (does not render CSS, execute JS, etc.)
      - Use for critical resources discovered late in the HTML (fonts referenced in CSS, images in CSS backgrounds)
      - **Warning:** Preloading too many resources dilutes the benefit -- limit to truly critical resources (LCP image, critical fonts)
      
      ### `prefetch`
      
      Fetches a resource at low priority for a likely future navigation:
      
      ```html
      <link rel="prefetch" href="/next-page.html">
      <link rel="prefetch" href="/next-page-data.json">
      ```
      
      - Downloaded at lowest priority during idle time
      - Stored in the HTTP cache for future use
      - Use for resources needed on the next likely page (next step in a flow, frequently visited page)
      - Does not work if the user has Data Saver enabled
      
      ### `prerender` / Speculation Rules
      
      Modern browsers support the Speculation Rules API for prerendering entire pages:
      
      ```html
      <script type="speculationrules">
      {
        "prerender": [
          { "urls": ["/likely-next-page"] }
        ]
      }
      </script>
      ```
      
      - Renders the entire page in a hidden tab
      - Near-instant navigation when the user clicks
      - Expensive in terms of memory and bandwidth -- use only for high-confidence predictions
      
      ## Font Loading Strategies
      
      Web fonts create unique performance challenges because the browser may hide text while fonts load (Flash of Invisible Text, or FOIT) or show a jarring swap (Flash of Unstyled Text, or FOUT).
      
      ### `font-display` values
      
      ```css
      @font-face {
        font-family: 'Custom Font';
        src: url('font.woff2') format('woff2');
        font-display: swap;  /* Show fallback immediately, swap when ready */
      }
      ```
      
      | Value | Behavior | Best for |
      |-------|----------|----------|
      | `auto` | Browser default (usually FOIT for 3s) | Not recommended |
      | `block` | Invisible text for up to 3s, then swap | Icon fonts only |
      | `swap` | Show fallback immediately, swap when loaded | Body text, most use cases |
      | `fallback` | Brief invisible period (100ms), then fallback, then swap | Balanced approach |
      | `optional` | Brief invisible period, uses font only if cached | Performance-critical; accepts FOUT |
      
      ### Preloading fonts
      
      Fonts referenced in CSS are discovered late -- the browser must load HTML, then CSS, then discover the font URL. Preloading jumps the queue:
      
      ```html
      <link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
      ```
      
      The `crossorigin` attribute is required even for same-origin fonts because the font specification requires CORS.
      
      ### Font subsetting
      
      Subset fonts to include only the characters needed:
      - Latin-only sites can drop CJK, Cyrillic, and other ranges
      - Use `unicode-range` in `@font-face` to load character ranges on demand
      
      ```css
      @font-face {
        font-family: 'Custom Font';
        src: url('font-latin.woff2') format('woff2');
        unicode-range: U+0000-00FF;  /* Basic Latin */
      }
      ```
      
      ## Image Optimization
      
      Images typically account for 50-70% of total page weight.
      
      ### Lazy loading
      
      ```html
      <!-- Browser-native lazy loading -->
      <img src="photo.jpg" loading="lazy" alt="Description">
      
      <!-- Do NOT lazy-load above-fold images (LCP candidates) -->
      <img src="hero.jpg" loading="eager" fetchpriority="high" alt="Hero">
      ```
      
      ### Responsive images
      
      Serve appropriately sized images for each viewport:
      
      ```html
      <img
        srcset="photo-400.webp 400w,
                photo-800.webp 800w,
                photo-1200.webp 1200w"
        sizes="(max-width: 600px) 400px,
               (max-width: 1000px) 800px,
               1200px"
        src="photo-800.webp"
        alt="Description"
      >
      ```
      
      ### Modern formats
      
      | Format | Compression | Browser support | Best for |
      |--------|-------------|-----------------|----------|
      | **AVIF** | Best (50% smaller than JPEG) | Chrome, Firefox, Safari 16+ | Photos, complex images |
      | **WebP** | Good (25-35% smaller than JPEG) | All modern browsers | General purpose |
      | **JPEG** | Baseline | Universal | Fallback |
      | **PNG** | Lossless | Universal | Transparency, screenshots |
      | **SVG** | Vector (tiny for icons) | Universal | Icons, logos, illustrations |
      
      Use the `<picture>` element for format negotiation:
      
      ```html
      <picture>
        <source srcset="photo.avif" type="image/avif">
        <source srcset="photo.webp" type="image/webp">
        <img src="photo.jpg" alt="Description">
      </picture>
      ```
      
      ### Explicit dimensions
      
      Always set width and height (or use CSS `aspect-ratio`) to prevent layout shift:
      
      ```html
      <img src="photo.jpg" width="800" height="600" alt="Description">
      ```
      
      ```css
      img {
        aspect-ratio: 4 / 3;
        width: 100%;
        height: auto;
      }
      ```
      
      ## Resource Loading Priority
      
      Modern browsers assign priorities based on resource type and position. The `fetchpriority` attribute gives developers explicit control:
      
      ```html
      <!-- High priority for LCP image -->
      <img src="hero.webp" fetchpriority="high" alt="Hero">
      
      <!-- Low priority for below-fold image -->
      <img src="footer-bg.webp" fetchpriority="low" loading="lazy" alt="">
      
      <!-- High priority for critical script -->
      <script src="critical.js" fetchpriority="high"></script>
      ```
      
      ### Default browser priorities
      
      | Resource | Default priority |
      |----------|-----------------|
      | HTML document | Highest |
      | CSS in `<head>` | Highest |
      | Preloaded resources | High |
      | Scripts in `<head>` | High |
      | Images in viewport | High |
      | Scripts at end of body | Medium |
      | Images out of viewport | Low |
      | Prefetched resources | Lowest |
      
      ## Practical Loading Strategy
      
      A complete resource loading strategy for a typical page:
      
      ```html
      <head>
        <!-- 1. DNS prefetch for third-party origins -->
        <link rel="dns-prefetch" href="https://analytics.example.com">
      
        <!-- 2. Preconnect to critical origins -->
        <link rel="preconnect" href="https://cdn.example.com">
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
      
        <!-- 3. Preload critical resources discovered late -->
        <link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
        <link rel="preload" href="/hero.avif" as="image">
      
        <!-- 4. Inline critical CSS -->
        <style>/* above-fold styles */</style>
      
        <!-- 5. Async load full stylesheet -->
        <link rel="preload" href="/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
      
        <!-- 6. Deferred application scripts -->
        <script src="/app.js" defer></script>
      
        <!-- 7. Async independent scripts -->
        <script src="/analytics.js" async></script>
      </head>
      <body>
        <!-- 8. LCP element with high priority -->
        <img src="/hero.avif" fetchpriority="high" alt="Hero" width="1200" height="600">
      
        <!-- 9. Below-fold images lazy loaded -->
        <img src="/feature.webp" loading="lazy" alt="Feature" width="800" height="400">
      
        <!-- 10. Prefetch next page for likely navigation -->
        <link rel="prefetch" href="/next-page.html">
      </body>
      ```
      
      This ordering ensures critical resources load first, nothing blocks rendering unnecessarily, and resources for future navigations are prepared during idle time.
      
  • SKILL.md 15.7 KB
    ---
    name: high-perf-browser
    description: 'Optimize web performance through network protocols, resource loading, and browser rendering internals. Use when the user mentions "my site is slow", "Core Web Vitals", "HTTP/2 or HTTP/3", "resource hints", "network latency", "render blocking", "TCP/TLS optimization", "service worker", "Cache-Control or caching strategy", or "critical rendering path". Also trigger when diagnosing slow page loads, optimizing time to first byte, choosing between WebSocket and SSE, or reducing bundle sizes. For UI visual performance, see refactoring-ui. For font loading, see web-typography.'
    license: MIT
    metadata:
      author: wondelai
      version: "1.4.1"
    ---
    
    # High Performance Browser Networking Framework
    
    A systematic approach to web performance grounded in how browsers, protocols, and networks actually work. Apply these principles when building frontend applications, setting performance budgets, configuring servers, or diagnosing slow page loads.
    
    ## Core Principle
    
    **Latency, not bandwidth, is the bottleneck.** Most web performance problems stem from too many round trips, not too little throughput. A 5x bandwidth increase yields diminishing returns; a 5x latency reduction transforms the user experience.
    
    **The foundation:** Every request passes through DNS resolution, TCP handshake, TLS negotiation, and HTTP exchange before a single byte of content arrives — each step adding round-trip latency. High-performance applications minimize round trips, parallelize requests, and eliminate unnecessary network hops. Understanding the protocol stack is the prerequisite for meaningful optimization.
    
    ## Scoring
    
    **Goal: 10/10.** Score by how many of the eight Quick Diagnostic rows pass, weighted toward the field metrics: **9-10** = all eight pass (the four field-metric rows in the green plus content-hashing, HTTP/2+, minimized render-blocking, and compression); **5-6** = the four field-metric rows pass but one or more transport/caching/compression rows fail; **<=3** = any field-metric row is in the red. Always report the score, which diagnostic rows failed, and the specific fix for each.
    
    ## The High Performance Browser Networking Framework
    
    Six domains for building fast, resilient web applications:
    
    ### 1. Network Fundamentals
    
    **Core concept:** Every HTTP request pays a latency tax — DNS lookup, TCP three-way handshake, TLS negotiation — before any application data flows. Reducing or eliminating these round trips is the single highest-leverage optimization.
    
    **Why it works:** Light travels at a finite speed: a New York–London packet takes ~28ms one way regardless of bandwidth. These physics-level constraints cannot be solved with bigger pipes — only with fewer trips.
    
    **Key insights:**
    - TCP three-way handshake adds one full RTT before data transfer begins
    - TCP slow start limits initial throughput to ~14KB (10 segments) in the first round trip — keep critical resources under this threshold
    - Upgrade to TLS 1.3: it halves the handshake round trips of TLS 1.2 and enables 0-RTT resumption for returning visitors
    - Head-of-line blocking in TCP means one lost packet stalls all streams on that connection
    - Bandwidth-delay product caps in-flight data; high-latency links underutilize bandwidth
    
    **Code applications:**
    
    | Context | Pattern | Example |
    |---------|---------|---------|
    | **Connection warmup** | Pre-establish connections to critical origins | `<link rel="preconnect" href="https://cdn.example.com">` |
    | **DNS prefetch** | Resolve third-party domains early (saves 20-120ms) | `<link rel="dns-prefetch" href="https://analytics.example.com">` |
    | **TLS optimization** | TLS 1.3 + session resumption | `ssl_protocols TLSv1.3;` with session tickets |
    | **Connection reuse** | Keep-alive avoids repeated handshakes | `Connection: keep-alive` (default in HTTP/1.1+) |
    
    See [references/network-fundamentals.md](references/network-fundamentals.md) when tuning servers or diagnosing handshake latency — the full TLS 1.2-vs-1.3 RTT derivation, slow-start doubling table, initcwnd/BDP math, OCSP-stapling Nginx config, and the DNS cache hierarchy.
    
    ### 2. HTTP Protocol Evolution
    
    **Core concept:** HTTP evolved from a simple request-response protocol into a multiplexed, binary system. Choosing the right protocol version and configuring it properly eliminates entire categories of performance problems.
    
    **Why it works:** HTTP/1.1 forces workarounds (domain sharding, sprites, concatenation) because it cannot multiplex. HTTP/2 multiplexes but inherits TCP head-of-line blocking; HTTP/3 (QUIC over UDP) eliminates it. Each generation removes a bottleneck — and makes the previous generation's workarounds counterproductive.
    
    **Key insights:**
    - HTTP/1.1 allows one outstanding request per TCP connection; browsers open 6 per host as a workaround
    - HTTP/2 multiplexes unlimited streams over one connection — domain sharding becomes counterproductive
    - HPACK header compression in HTTP/2 cuts repetitive header overhead by 85-95%
    - HTTP/3 (QUIC) eliminates TCP head-of-line blocking and enables 0-RTT resumption and connection migration
    - Prefer `103 Early Hints` over HTTP/2 Server Push (which over-pushes and is widely deprecated)
    - Connection coalescing lets one HTTP/2 connection serve multiple hostnames sharing a certificate
    
    **Code applications:**
    
    | Context | Pattern | Example |
    |---------|---------|---------|
    | **HTTP/2 migration** | Remove HTTP/1.1 workarounds | Undo domain sharding, sprites, file concatenation |
    | **103 Early Hints** | Send preload hints before the full response | `103` with `Link: </style.css>; rel=preload` |
    | **QUIC/HTTP/3** | Advertise HTTP/3 on CDN or origin | `Alt-Svc: h3=":443"` header |
    | **Stream prioritization** | Signal resource importance | CSS and fonts highest priority; images lower |
    
    See [references/http-protocols.md](references/http-protocols.md) when picking or migrating a protocol version — side-by-side HTTP/1.1-vs-2-vs-3 comparison, the step-by-step de-sharding migration, and why Server Push lost to 103 Early Hints.
    
    ### 3. Resource Loading and Critical Rendering Path
    
    **Core concept:** The browser must build the DOM, CSSOM, and render tree before painting pixels: HTML → DOM → CSSOM → Render Tree → Layout → Paint → Composite. Any resource that blocks this pipeline delays first paint.
    
    **Why it works:** CSS is render-blocking (no paint until CSSOM is ready) while JavaScript is parser-blocking (`<script>` halts DOM construction until it downloads and executes) — so each needs a different optimization strategy. Every blocking resource adds latency directly to time-to-first-paint.
    
    **Key insights:**
    - `async` downloads in parallel and executes immediately (use for independent scripts); `defer` downloads in parallel but executes after DOM parsing (use for most scripts)
    - `<link rel="preload">` fetches critical resources at high priority now; `rel="prefetch"` fetches likely next-navigation resources at low priority
    - Inline above-the-fold CSS and async-load the rest to eliminate the render-blocking CSS request
    - Fonts can block text rendering for up to 3s — use `font-display: swap`
    
    **Code applications:**
    
    | Context | Pattern | Example |
    |---------|---------|---------|
    | **Critical CSS** | Inline above-the-fold styles in `<head>` | `<style>/* critical */</style>` + async full CSS |
    | **Script loading** | `defer` by default; `async` for independents | `<script src="app.js" defer></script>` |
    | **Resource hints** | Preload critical fonts, hero images | `<link rel="preload" href="font.woff2" as="font" crossorigin>` |
    | **Image optimization** | Lazy-load below-fold; modern formats | `<img loading="lazy" src="photo.avif" srcset="...">` |
    
    See [references/resource-loading.md](references/resource-loading.md) when shaving first paint — the exact async/defer/module execution order, the full resource-hint decision tree, and the image/font (`font-display`, `srcset`, AVIF) playbook.
    
    ### 4. Caching Strategies
    
    **Core concept:** The fastest network request is one that never happens. Layer caches — browser memory, disk, service worker, CDN, origin — to eliminate round trips for repeat visitors.
    
    **Why it works:** Cache-Control headers tell the browser and intermediaries exactly how long a response stays valid; content-hashed URLs make aggressive immutable caching safe. Each cache hit eliminates a full network round trip.
    
    **Key insights:**
    - `Cache-Control: no-cache` still caches but revalidates every time; `no-store` never caches — don't confuse them
    - `ETag` / `Last-Modified` enable conditional requests (`304 Not Modified`) that skip the body transfer
    - Service workers provide a programmable cache layer that works offline (cache-first shell, network-first dynamic content)
    - Misconfigured `Vary` headers cause CDN cache pollution — serve the wrong encoding or format to the wrong client
    
    **Code applications:**
    
    | Context | Pattern | Example |
    |---------|---------|---------|
    | **Static assets** | Immutable cache + hash busting | `style.a1b2c3.css` with `Cache-Control: max-age=31536000, immutable` |
    | **HTML documents** | Revalidate on every request | `Cache-Control: no-cache` with `ETag` |
    | **API responses** | Short TTL + background refresh | `Cache-Control: max-age=60, stale-while-revalidate=3600` |
    | **CDN config** | Cache at edge with correct Vary | `Vary: Accept-Encoding, Accept` |
    
    See [references/caching-strategies.md](references/caching-strategies.md) when designing a cache policy — the full browser/SW/CDN/origin hierarchy, copy-paste service-worker cache-first vs network-first recipes, and the `Vary` pitfalls that pollute a CDN.
    
    ### 5. Core Web Vitals Optimization
    
    **Core concept:** Core Web Vitals — LCP, INP, CLS — are Google's user-centric metrics covering loading, interactivity, and visual stability. They impact search ranking and reflect real user experience.
    
    **Why it works:** A fast TTFB means nothing if the hero image still loads late (LCP) or main-thread JavaScript blocks interactions (INP) — so server-side timing can look green while users wait. Optimize the perceived milestones, not the byte-delivery clock.
    
    **Key insights** (numeric pass/fail thresholds live in the Quick Diagnostic):
    - LCP — optimize the largest visible element (hero image, heading block, video poster)
    - INP — keep the main thread free; break long tasks so every interaction (not only the first) stays responsive
    - CLS — reserve space for dynamic content before it loads
    - TTFB and FCP (< 1.8s) are upstream gates: they bound every downstream milestone, so fix them first
    - Measure with Real User Monitoring (RUM) in production — lab/synthetic tests miss real-device and network variance
    
    **Code applications:**
    
    | Context | Pattern | Example |
    |---------|---------|---------|
    | **LCP** | Preload LCP element; raise its priority | `<img src="hero.webp" fetchpriority="high">` |
    | **INP** | Break long tasks; yield to main thread | `scheduler.yield()` or `setTimeout` chunking |
    | **CLS** | Reserve space for async content | `<img width="800" height="600">` or CSS `aspect-ratio` |
    | **Performance budget** | Fail CI when a vital regresses past its Quick Diagnostic threshold | Lighthouse CI assertions on LCP/INP/CLS |
    
    See [references/core-web-vitals.md](references/core-web-vitals.md) when a metric is in the red — per-metric debugging workflows (what to inspect for a bad LCP/INP/CLS), the lab-vs-RUM tooling map, and per-vital optimization checklists.
    
    ### 6. Real-Time Communication
    
    **Core concept:** When data must flow continuously, the transport choice — WebSocket, SSE, or long polling — determines latency, resource usage, and scalability.
    
    **Why it works:** HTTP's request-response model adds overhead to every real-time update. WebSocket offers full-duplex with ~2-byte framing; SSE offers simpler server-to-client push over plain HTTP. Match the transport to the data flow direction and frequency instead of defaulting to the most powerful option.
    
    **Key insights:**
    - WebSocket: bidirectional (chat, gaming, collaborative editing); SSE: server-to-client only, auto-reconnects, proxy-friendly, simpler
    - Long polling is a fallback only — high overhead from repeated HTTP requests
    - Each WebSocket is a separate TCP connection that bypasses HTTP/2 multiplexing
    - Send heartbeat/ping frames — mobile networks silently drop idle connections
    - Reconnect with exponential backoff and queue messages while disconnected
    
    **Code applications:**
    
    | Context | Pattern | Example |
    |---------|---------|---------|
    | **Chat / collaboration** | WebSocket + heartbeat + reconnection | `new WebSocket('wss://...')` with ping every 30s |
    | **Live feeds / notifications** | SSE for server-to-client streaming | `new EventSource('/api/updates')` |
    | **Connection resilience** | Exponential backoff on reconnect | 1s, 2s, 4s, 8s... capped at 30s |
    | **Scaling** | Pub/sub broker behind WebSocket servers | Redis Pub/Sub or NATS |
    
    See [references/real-time-communication.md](references/real-time-communication.md) when building a live feature — the WebSocket connect/heartbeat/reconnect lifecycle, the SSE `EventSource` pattern, and how to scale fan-out behind a pub/sub broker.
    
    ## Common Mistakes
    
    | Mistake | Why It Fails | Fix |
    |---------|-------------|-----|
    | Adding bandwidth to fix slow pages | Latency is the bottleneck, not throughput | Reduce round trips: preconnect, cache, CDN |
    | Loading all JS upfront | Parser-blocking scripts delay paint and interactivity | Code-split; `defer`; lazy-load non-critical modules |
    | No resource hints | Browser discovers critical resources too late | `preconnect` + `preload` for above-fold criticals |
    | Missing Cache-Control / `no-store` everywhere | Every visit re-downloads everything | Proper `max-age` + content hashing |
    | Ignoring CLS | Layout shifts destroy trust and ranking | Explicit dimensions on images, embeds, ads |
    | WebSocket for everything | Needless complexity when SSE/polling suffices | Match transport to data flow; SSE for server push |
    | Domain sharding on HTTP/2 | Defeats multiplexing; extra TCP connections | Consolidate origins; let HTTP/2 multiplex |
    | No compression | Text resources transfer at full size | Enable Brotli (preferred) or Gzip on server/CDN |
    
    ## Quick Diagnostic
    
    | Question | If No | Action |
    |----------|-------|--------|
    | Is TTFB under 800ms? | Server or network too slow | CDN, server caching, check backend |
    | Is LCP under 2.5s? | Largest element loads too late | Preload LCP resource; `fetchpriority="high"` |
    | Is INP under 200ms? | Main thread blocked | Break long tasks; defer non-critical JS |
    | Is CLS under 0.1? | Elements shift after render | Explicit dimensions; reserve space |
    | Are static assets content-hashed and cached? | Repeat visitors re-download | Hashed filenames + `Cache-Control: immutable` |
    | Is HTTP/2 or HTTP/3 enabled? | No multiplexing or header compression | Enable HTTP/2 on server; HTTP/3 via CDN |
    | Are render-blocking resources minimized? | CSS and sync JS delay first paint | Inline critical CSS; `defer` scripts; prune unused CSS |
    | Is compression enabled (Brotli/Gzip)? | Uncompressed text transfers | Enable Brotli on server/CDN; Gzip fallback |
    
    ## Further Reading
    
    Based on Ilya Grigorik's comprehensive guide to browser networking and web performance:
    
    - [*"High Performance Browser Networking"*](https://www.amazon.com/High-Performance-Browser-Networking-performance/dp/1449344763?tag=wondelai00-20) by Ilya Grigorik (the complete reference for networking protocols, browser internals, and performance optimization)
    - [hpbn.co](https://hpbn.co/) -- Free online edition maintained by the author
    
    ## About the Author
    
    **Ilya Grigorik** is a web performance engineer who spent over a decade at Google working on Chrome, web platform performance, and HTTP standards, and co-chaired the W3C Web Performance Working Group. His book *High Performance Browser Networking* (O'Reilly, 2013) is widely regarded as the definitive reference on how browsers interact with the network.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related