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.
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:
- Server‑side rendering latency for data‑rich React Server Components (RSC) that must travel from origin to edge on every request.
- 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
| Metric | Baseline (origin only) | Edge‑cached (SWR) | Δ |
|---|---|---|---|
| INP (p90) | 210 ms | 78 ms | -62 ms |
| LCP (p90) | 2.8 s | 1.4 s | -1.4 s |
| TTFB | 420 ms | 95 ms | -325 ms |
| Cloudflare KV reads | – | ~0.5 ms per request | negligible |
| 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‑storeheader 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‑Cookieheaders from cached responses to avoid leaking session data across users. - Cost scaling – KV pricing is linear with reads; high‑traffic sites should monitor
kv_readsmetrics 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
INPin 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:
- Serves static assets from KV.
- Implements stale‑while‑revalidate caching for RSC routes.
- 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
- [1] Interaction to Next Paint (INP) – Web.dev[https://web.dev/articles/inp]
- [2] Largest Contentful Paint (LCP) – Web.dev[https://web.dev/articles/lcp]
- [3] Cloudflare Workers documentation[https://developers.cloudflare.com/workers/]
- [4] React Server Components – Official docs[https://react.dev/reference/rsc]
- [5] Core Web Vitals overview – Web.dev[https://web.dev/articles/vitals]