: Promise<void> {
if ('scheduler' in window && typeof window.scheduler.yield === 'function') {
return window.scheduler.yield();
}
return new Promise(resolve => setTimeout(resolve, 0));
}
async function processUserQuery(query: string): Promise<void> {
// 1. Immediate UI feedback
updateSearchUI(query);
await yieldToEventLoop();
// 2. Heavy computation
const matches = await computeSearchMatches(query);
await yieldToEventLoop();
// 3. State commit
commitSearchResults(matches);
}
**Layer 2: Deferred Value Rendering**
```typescript
import { useDeferredValue, useMemo } from 'react';
interface SearchGridProps {
dataset: Product[];
activeFilter: string;
}
export function SearchGrid({ dataset, activeFilter }: SearchGridProps) {
const deferredDataset = useDeferredValue(dataset);
const filteredView = useMemo(() => {
return deferredDataset.filter(item =>
item.category === activeFilter
);
}, [deferredDataset, activeFilter]);
return (
<div className="grid-layout">
{filteredView.map(item => (
<ProductTile key={item.sku} data={item} />
))}
</div>
);
}
Layer 3: Telemetry Offloading
// telemetry-dispatcher.ts
const telemetryWorker = new Worker(new URL('./telemetry-worker.ts', import.meta.url));
export function dispatchMetric(metric: PerformanceMetric): void {
telemetryWorker.postMessage({
action: 'flush',
payload: {
name: metric.name,
value: metric.value,
context: {
route: window.location.pathname,
viewport: window.innerWidth,
connection: (navigator as any).connection?.effectiveType || 'unknown'
}
}
});
}
// telemetry-worker.ts
self.onmessage = async (event: MessageEvent) => {
if (event.data.action === 'flush') {
const { payload } = event.data;
const serialized = JSON.stringify(payload);
await fetch('/api/metrics', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: serialized,
keepalive: true
});
}
};
Architecture Rationale: INP rewards handlers that perform minimal synchronous work. By yielding after UI updates, deferring expensive derivations, and offloading network serialization to workers, the main thread remains available for paint and input processing. This pattern maintains application responsiveness without altering core business logic.
Fix 2: LCP β Prioritizing the Critical Rendering Path
LCP optimization requires identifying the single element that constitutes the largest visible content block in the initial viewport and ensuring its delivery path is unobstructed.
Step 1: Runtime LCP Identification
import { onLCP } from 'web-vitals';
export function trackCriticalPaint(): void {
onLCP((metric) => {
const lcpEntry = metric.entries.at(-1);
const targetElement = lcpEntry?.element;
console.debug('Critical element:', targetElement?.tagName, targetElement?.className);
console.debug('Paint latency:', metric.value);
reportToTelemetry({
metric: 'LCP',
value: metric.value,
element: targetElement?.outerHTML?.slice(0, 200)
});
});
}
Step 2: Resource Preloading & Priority Assignment
<head>
<link rel="preload" as="image" href="/assets/hero-landscape.avif" type="image/avif" />
<link rel="preload" as="font" href="/fonts/brand-sans.woff2" type="font/woff2" crossorigin />
</head>
Step 3: Responsive Delivery with Explicit Dimensions
<picture>
<source
type="image/avif"
srcset="/assets/hero-480.avif 480w, /assets/hero-800.avif 800w, /assets/hero-1200.avif 1200w"
sizes="(max-width: 768px) 100vw, 1200px"
/>
<source
type="image/webp"
srcset="/assets/hero-480.webp 480w, /assets/hero-800.webp 800w, /assets/hero-1200.webp 1200w"
sizes="(max-width: 768px) 100vw, 1200px"
/>
<img
src="/assets/hero-1200.jpg"
fetchpriority="high"
alt="Platform overview"
width="1200"
height="600"
decoding="async"
/>
</picture>
Step 4: Font Preload for Text-Heavy LCP
@font-face {
font-family: 'BrandSans';
src: url('/fonts/brand-sans.woff2') format('woff2');
font-weight: 400 700;
font-display: swap;
}
Architecture Rationale: LCP is strictly bound to network priority and decoding time. Preloading eliminates the discovery delay, fetchpriority="high" forces the browser to deprioritize non-critical assets, and explicit dimensions prevent layout recalculation. Lazy-loading must never apply to initial viewport content.
Fix 3: CLS β Layout Stabilization Protocols
CLS measures unexpected visual displacement. The fix requires deterministic layout reservation and metric-aligned typography.
Rule 1: Deterministic Container Sizing
.media-container {
aspect-ratio: 16 / 9;
width: 100%;
background-color: var(--skeleton-base);
}
.dynamic-slot {
min-height: 280px;
display: flex;
align-items: center;
justify-content: center;
}
Rule 2: Font Metric Alignment
@font-face {
font-family: 'BrandSans';
src: local('system-ui'), local('Arial');
font-weight: 400;
font-display: swap;
size-adjust: 102.4%;
ascent-override: 96%;
descent-override: 24%;
line-gap-override: 0%;
}
@font-face {
font-family: 'BrandSans';
src: url('/fonts/brand-sans.woff2') format('woff2');
font-weight: 400;
font-display: swap;
}
Architecture Rationale: CLS is resolved through spatial reservation and typographic consistency. aspect-ratio and min-height guarantee the browser allocates exact space before network resolution. Font override descriptors ensure the fallback typeface occupies identical vertical space, eliminating swap-induced jumps.
Pitfall Guide
-
Lab-First Optimization
- Explanation: Chasing synthetic scores ignores network variance, device fragmentation, and third-party interference. A 95+ Lighthouse score often masks poor 75th-percentile CrUX data.
- Fix: Anchor optimization targets to field telemetry. Use synthetic tools only for regression detection, not primary KPIs.
-
Preload Queue Flooding
- Explanation: Applying
<link rel="preload"> to non-critical assets saturates the network pipeline, starving the actual LCP element and delaying paint.
- Fix: Preload only the verified LCP resource and critical fonts. Audit preload directives quarterly using network waterfall analysis.
-
Hero Element Lazy-Loading
- Explanation: Applying
loading="lazy" to initial viewport content forces the browser to defer fetching until scroll intersection, directly violating LCP thresholds.
- Fix: Reserve
loading="lazy" exclusively for below-the-fold assets. Verify hero images use fetchpriority="high" and omit lazy attributes.
-
Aggregate Metric Masking
- Explanation: Site-wide CWV averages obscure severe regressions on specific routes, device classes, or geographic regions. A 0.05 CLS average can hide 0.25 shifts on checkout pages.
- Fix: Slice telemetry by route, viewport class, and connection type. Implement route-specific alerting thresholds.
-
Font Swap Layout Jumps
- Explanation: Using
font-display: swap without aligning fallback and web font metrics causes visible content displacement when the custom font loads.
- Fix: Apply
size-adjust, ascent-override, descent-override, and line-gap-override to the fallback @font-face block to lock vertical rhythm.
-
Third-Party Main-Thread Blocking
- Explanation: Analytics, personalization engines, and chat widgets executing synchronous serialization or DOM manipulation during user interactions destroy INP.
- Fix: Offload all third-party telemetry and heavy computation to Web Workers. Use
requestIdleCallback or scheduler.yield() for non-critical DOM updates.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| High interaction frequency (filters, search) | scheduler.yield() + useDeferredValue | Prevents main-thread starvation during rapid input | Low (framework-native) |
| Heavy data processing (sorting, aggregation) | Web Worker offloading | Moves computation off critical path entirely | Medium (worker setup + serialization) |
| Hero image dominates initial viewport | preload + fetchpriority="high" + AVIF | Eliminates discovery delay and forces network priority | Low (HTML/CSS only) |
| Dynamic ad/feature slots | min-height reservation + skeleton UI | Guarantees layout stability before async injection | Low (CSS only) |
| Custom typography with visible swap | size-adjust + override descriptors | Locks fallback vertical metrics to web font | Low (CSS only) |
| Third-party analytics/personalization | Worker dispatch + sendBeacon | Prevents sync blocking on user interactions | Medium (infrastructure routing) |
Configuration Template
// vitals-telemetry.config.ts
import { onINP, onLCP, onCLS } from 'web-vitals';
const THRESHOLDS = {
INP: 160,
LCP: 2000,
CLS: 0.08
};
function getDeviceClass(): string {
const ua = navigator.userAgent;
if (/Mobi|Android/i.test(ua)) return 'mobile';
if (/Tablet|iPad/i.test(ua)) return 'tablet';
return 'desktop';
}
function getNetworkClass(): string {
const conn = (navigator as any).connection;
return conn?.effectiveType || 'unknown';
}
function dispatchMetric(metric: any): void {
const payload = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
route: window.location.pathname,
device: getDeviceClass(),
network: getNetworkClass(),
timestamp: Date.now()
});
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/vitals', payload);
} else {
fetch('/api/vitals', { method: 'POST', body: payload, keepalive: true });
}
// Proactive alerting hook
const threshold = THRESHOLDS[metric.name as keyof typeof THRESHOLDS];
if (threshold && metric.value > threshold) {
console.warn(`[Vitals] ${metric.name} exceeded threshold: ${metric.value}ms`);
}
}
onINP(dispatchMetric);
onLCP(dispatchMetric);
onCLS(dispatchMetric);
export { THRESHOLDS };
Quick Start Guide
- Install telemetry: Add
web-vitals to your project and import the configuration template. Mount the instrumentation in your application entry point.
- Identify critical assets: Run field instrumentation for 7 days. Extract the top LCP element and verify its network priority.
- Apply layout reservations: Audit all media, iframes, and dynamic slots. Add explicit dimensions or
min-height constraints to prevent CLS.
- Implement yielding: Wrap synchronous event handlers with
scheduler.yield() or deferred rendering patterns. Offload telemetry to a Web Worker.
- Validate in field: Monitor CrUX-aligned telemetry for 14 days. Confirm INP <160ms, LCP <2.0s, and CLS <0.08 across target device classes. Iterate on route-specific regressions.