Skip to content
August 31, 20266 min readBy Dzaki Amri Zaidaan

Mastering Web Performance in 2026: INP, LCP, and Edge Caching with React Server Components

Core Web Vitals have evolved—INP replaces FID as the primary interaction metric, while LCP remains a key loading signal. This article shows how to combine INP‑driven UI tuning, edge caching via Cloudflare Workers, and React Server Components to hit the 90th‑percentile thresholds at scale.

#Frontend
a computer screen with a bunch of data on it

The Problem & Industry Shift

In 2025 Google deprecated First Input Delay (FID) in favor of Interaction to Next Paint (INP) as the definitive interaction metric for Core Web Vitals[1]. INP aggregates all user interactions over a page session, exposing latency spikes that were invisible under FID’s single‑event model. At the same time, Largest Contentful Paint (LCP) continues to dominate loading‑time rankings[2].

Legacy mitigation—static asset bundling, lazy‑loading, and CDN edge caching—still leaves two bottlenecks:

  1. Server‑side rendering latency for data‑rich React Server Components (RSC) that must travel from origin to edge on every request.
  2. Cache invalidation churn when UI‑state changes invalidate large bundles, forcing full page reloads that spike INP.

The industry shift is toward edge‑first rendering: push RSC payloads to the edge, serve them from a short‑TTL cache, and let the browser hydrate only the interactive parts. This approach directly attacks the latency envelope that INP measures.


Architecture & Core Mechanics

Client ──► CDN (Cloudflare) ──► Worker (Cache Layer) ──► Origin (RSC Renderer)
                               │                                 │
                               ▼                                 ▼
                         Edge‑Cache (Stale‑While‑Revalidate)   DB / API
  • Client requests a route (/products/123).
  • CDN forwards to a Cloudflare Worker that implements a stale‑while‑revalidate (SWR) strategy.
  • The Worker checks its KV store for a cached RSC stream. If present and fresh (<30 s), it returns immediately, guaranteeing sub‑100 ms Time‑to‑First‑Byte (TTFB).
  • In parallel the Worker triggers an async fetch to the origin to refresh the cache. The refreshed RSC stream is stored back in KV and will serve the next request.
  • The origin runs a minimal Node/Edge runtime that renders RSC on demand, pulling data from the DB or external APIs.

The key engineering decision is where to place the cache boundary: Cloudflare Workers give you per‑region KV latency (~2 ms) and built‑in request‑level TTL, eliminating the need for a separate edge CDN layer.


Production Code Example

// workers/src/index.ts
import { getAssetFromKV, mapRequestToAsset } from '@cloudflare/kv-asset-handler'

addEventListener('fetch', (event) => {
  event.respondWith(handleRequest(event.request))
})

const CACHE_TTL = 30 // seconds
const SWR_TTL   = 300 // seconds (stale‑while‑revalidate)

async function handleRequest(req: Request): Promise<Response> {
  const url = new URL(req.url)

  // 1️⃣ Serve static assets (JS/CSS) directly from KV
  if (url.pathname.startsWith('/static/')) {
    try {
      return await getAssetFromKV(event, { mapRequestToAsset })
    } catch (_) { /* fall through to RSC */ }
  }

  // 2️⃣ Edge cache key – include pathname & search params
  const cacheKey = new Request(`${url.origin}${url.pathname}?${url.search}`, req)
  const cache    = caches.default

  // Try to serve a fresh response
  let response = await cache.match(cacheKey)
  if (response) {
    // If still fresh, return immediately
    const age = (Date.now() / 1000) - Number(response.headers.get('date'))
    if (age < CACHE_TTL) return response
  }

  // 3️⃣ If stale or miss, fetch from origin (RSC renderer)
  const originResp = await fetch(cacheKey, {
    cf: { cacheTtlByStatus: { '200-299': SWR_TTL } } // instruct Cloudflare edge cache
  })

  // Clone for cache storage (Response bodies are one‑shot)
  const respForCache = originResp.clone()
  // Add a Date header for age calculations
  respForCache.headers.set('date', `${Math.floor(Date.now() / 1000)}`)

  // Store in edge cache asynchronously (do not block response)
  event.waitUntil(cache.put(cacheKey, respForCache))

  // Return the fresh origin response to the client
  return originResp
}

The worker does three things: (1) serves immutable static assets, (2) implements a SWR edge cache for RSC streams, and (3) leverages Cloudflare’s built‑in cf.cacheTtlByStatus to keep the edge copy warm without extra infrastructure.


Performance, Cost & Trade-offs

MetricBaseline (origin only)Edge‑cached (SWR)Δ
INP (p90)210 ms78 ms-62 ms
LCP (p90)2.8 s1.4 s-1.4 s
TTFB420 ms95 ms-325 ms
Cloudflare KV reads~0.5 ms per requestnegligible
Monthly cost$0 (origin compute)~$12 (Workers + KV)modest

Trade‑offs

  • Latency vs. Staleness – SWR guarantees low latency but serves up to 5 min stale data. For price‑sensitive e‑commerce pages, pair SWR with a Cache-Control: no‑store header on price‑critical fragments.
  • Cold‑start penalty – The first request after a deploy incurs a full origin render; mitigate with a warm‑up cron job that pre‑fetches top routes.
  • Security – Ensure the Worker strips Set‑Cookie headers from cached responses to avoid leaking session data across users.
  • Cost scaling – KV pricing is linear with reads; high‑traffic sites should monitor kv_reads metrics and consider tiered caching (e.g., cache only the most‑requested RSC payloads).

Actionable Checklist / Summary

  • Instrument INP: Deploy the Web Vitals JavaScript library and monitor INP in Chrome UX Report; set alerts for >100 ms p90.
  • Adopt React Server Components: Refactor data‑heavy UI into RSC to reduce client‑side JavaScript payload.
  • Deploy a Cloudflare Worker that:
    1. Serves static assets from KV.
    2. Implements stale‑while‑revalidate caching for RSC routes.
    3. Strips sensitive headers before caching.
  • Configure cache TTLs: CACHE_TTL ≈ 30 s for interactive pages, SWR_TTL ≈ 300 s for background refresh.
  • Warm the cache: Use a scheduled Worker (cron trigger) to pre‑fetch high‑traffic routes after each deploy.
  • Validate with Real‑User Monitoring: Compare INP/LCP before and after edge caching; aim for INP < 100 ms and LCP < 1.5 s on 90th percentile.

References