---
id: wiki-2026-0508-core-web-vitals-optimization
title: Core Web Vitals Optimization (INP, LCP, CLS)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Core Web Vitals, INP, LCP, CLS, FID, web performance, react concurrent, RSC]
duplicate_of: none
source_trust_level: A
confidence_score: 0.95
verification_status: applied
tags: [web-performance, core-web-vitals, lcp, inp, cls, react, ssr, rsc, lighthouse, seo]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
  language: JS / React / CSS
  framework: Next.js / React 19 / Lighthouse
---

# Core Web Vitals Optimization

## 매 한 줄
> **"매 LCP < 2.5s, INP < 200ms, CLS < 0.1"**. Google ranking + 매 user retention 의 currency. 매 modern: 매 RSC, concurrent rendering, AVIF, 매 Container Queries. 매 INP 의 2024+ FID 의 replace.

## 매 핵심 metric

### LCP (Largest Contentful Paint)
- 매 main content 의 paint.
- 매 target: < 2.5 sec.
- **Optimize**:
  - SSR / SSG (vs CSR).
  - Code splitting.
  - Image optim (AVIF, WebP, lazy load).
  - Preload critical asset.
  - CDN.
  - HTTP/3.

### INP (Interaction to Next Paint, 2024+)
- 매 worst interaction → next paint.
- 매 target: < 200 ms.
- **Optimize**:
  - Concurrent rendering (React `useTransition`, `useDeferredValue`).
  - Virtualization (long list).
  - Debounce / throttle.
  - Web Worker (heavy task off main thread).
  - RSC.
  - Avoid long task (>50ms).

### CLS (Cumulative Layout Shift)
- 매 layout shift 의 cumulative.
- 매 target: < 0.1.
- **Optimize**:
  - Image / video 의 width + height attribute.
  - `aspect-ratio` CSS.
  - Font 의 `font-display: optional` or `swap` with size-adjust.
  - Animation 의 transform 만.

### Other
- **TTFB** (Time to First Byte): server-side speed.
- **FCP** (First Contentful Paint): early paint.

### 매 React 19 / Next.js 14+ optimization
- **Server Components (RSC)**: 매 client JS 의 ↓.
- **`useTransition`**: 매 non-urgent update.
- **`useDeferredValue`**: 매 stale value 의 fallback.
- **Streaming**: 매 progressive HTML.
- **Suspense + `loading.tsx`**.

### 매 modern image
- **AVIF** (2026 baseline): 매 best compression.
- **WebP** (fallback).
- **`<picture>`** with multiple source.
- **Responsive `srcset`**.
- **Native `loading="lazy"`**.

### 매 measurement
- **Lab**: Lighthouse, PageSpeed Insights.
- **Field**: web-vitals library, RUM (Real User Monitoring).
- **CrUX** (Chrome UX Report).
- **Search Console**: 매 page experience.

## 💻 패턴

### web-vitals (field measurement)
```js
import { onLCP, onINP, onCLS, onTTFB, onFCP } from 'web-vitals';

function send(metric) {
  navigator.sendBeacon('/analytics', JSON.stringify({
    name: metric.name,
    value: metric.value,
    rating: metric.rating,  // good / needs-improvement / poor
    delta: metric.delta,
    id: metric.id,
    navigationType: metric.navigationType,
  }));
}

onLCP(send);
onINP(send);
onCLS(send);
onTTFB(send);
onFCP(send);
```

### React useTransition (INP)
```tsx
import { useTransition, useState } from 'react';

function FilterableList({ items }) {
  const [filter, setFilter] = useState('');
  const [deferredFilter, setDeferredFilter] = useState('');
  const [isPending, startTransition] = useTransition();
  
  const handleChange = (e) => {
    setFilter(e.target.value);  // 매 urgent
    startTransition(() => {
      setDeferredFilter(e.target.value);  // 매 non-urgent (heavy filter)
    });
  };
  
  const filtered = items.filter(item => item.includes(deferredFilter));
  
  return (
    <>
      <input value={filter} onChange={handleChange} />
      {isPending && <span>updating...</span>}
      <List items={filtered} />
    </>
  );
}
```

### Virtualization (react-virtuoso)
```tsx
import { Virtuoso } from 'react-virtuoso';

function LongList({ items }) {
  return (
    <Virtuoso
      style={{ height: '600px' }}
      totalCount={items.length}
      itemContent={(index) => <Row item={items[index]} />}
    />
  );
}
```

### React Server Component (Next.js 14+)
```tsx
// app/page.tsx (Server Component by default)
import { getProducts } from '@/lib/db';

export default async function Page() {
  const products = await getProducts();  // 매 server only
  return (
    <div>
      <h1>Products</h1>
      <ProductList products={products} />
      <ClientCart />  {/* 매 client component for interactivity */}
    </div>
  );
}
```

### Image optimization (Next.js)
```tsx
import Image from 'next/image';

<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={600}
  priority  // 매 LCP candidate — 매 preload
  sizes="(max-width: 768px) 100vw, 1200px"
/>

// 매 below-fold
<Image
  src="/below.jpg"
  alt="Below"
  width={800} height={600}
  loading="lazy"  // 매 default for non-priority
/>
```

### `<picture>` with AVIF / WebP fallback
```html
<picture>
  <source srcset="hero.avif" type="image/avif">
  <source srcset="hero.webp" type="image/webp">
  <img src="hero.jpg" alt="Hero" width="1200" height="600">
</picture>
```

### Preload critical
```html
<link rel="preload" as="image" href="/hero.avif" 
      imagesrcset="/hero-mobile.avif 600w, /hero.avif 1200w" 
      imagesizes="(max-width: 768px) 100vw, 1200px">
<link rel="preload" as="font" href="/fonts/inter.woff2" type="font/woff2" crossorigin>
```

### Font CLS-prevention
```css
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter.woff2') format('woff2');
  font-display: swap;
  size-adjust: 105%;  /* 매 fallback adjusted */
  ascent-override: 90%;
}

body { font-family: 'Inter', system-ui, sans-serif; }
```

### Aspect-ratio (CLS)
```css
.thumbnail {
  aspect-ratio: 16 / 9;
  width: 100%;
  height: auto;
}
```

### Web Worker (offload heavy task)
```js
// main.js
const worker = new Worker('worker.js');
worker.postMessage(largeArray);
worker.onmessage = (e) => render(e.data);

// worker.js
self.onmessage = (e) => {
  const result = heavyComputation(e.data);
  self.postMessage(result);
};
```

### Lighthouse CI
```yaml
# .github/workflows/lighthouse.yml
- uses: treosh/lighthouse-ci-action@v10
  with:
    urls: |
      https://staging.example.com/
      https://staging.example.com/products
    budgetPath: '.lighthouse/budget.json'
    runs: 5
```

```json
[{
  "performance": [
    { "metric": "largest-contentful-paint", "budget": 2500 },
    { "metric": "interaction-to-next-paint", "budget": 200 },
    { "metric": "cumulative-layout-shift", "budget": 0.1 }
  ]
}]
```

### React.memo + useMemo + useCallback
```tsx
const MemoizedRow = React.memo(({ item }) => <div>{item.name}</div>);

function List({ items, onSelect }) {
  // 매 filter 의 stable reference
  const visibleItems = useMemo(() => 
    items.filter(i => i.visible), [items]);
  
  // 매 handler 의 stable reference
  const handleSelect = useCallback((id) => onSelect(id), [onSelect]);
  
  return visibleItems.map(item => (
    <MemoizedRow key={item.id} item={item} onSelect={handleSelect} />
  ));
}
```

### Long task break (scheduler.yield)
```js
async function heavyLoop(items) {
  for (let i = 0; i < items.length; i++) {
    process(items[i]);
    if (i % 100 === 0) {
      // 매 yield to main thread
      await scheduler.yield?.() ?? new Promise(r => setTimeout(r, 0));
    }
  }
}
```

## 🤔 결정 기준
| Issue | Fix |
|---|---|
| LCP > 2.5s | SSR + image priority + preload |
| INP > 200ms | Concurrent render + virtualization + debounce |
| CLS > 0.1 | Image dimensions + aspect-ratio + font swap |
| TTFB slow | CDN + edge + caching |
| Long tasks | Web Worker / yield |
| Heavy interaction | useTransition + useDeferredValue |
| Long list | Virtualization |

**기본값**: SSR + AVIF/WebP + responsive image + concurrent render + Lighthouse CI.

## 🔗 Graph
- 부모: [[Web-Performance]] · [[SEO]] · [[Frontend]]
- 변형: [[LCP]] · [[INP]] · [[CLS]] · [[FCP]] · [[TTFB]]
- 응용: [[React]] · [[Next-js]] · [[RSC]] · [[Lighthouse]]
- Adjacent: [[CSS Animations]] · [[Container_Queries]] · [[Baseline-Project]] · [[Case-Study-Allbirds-PWA-Redesign]] · [[Chrome DevTools 메모리 프로파일링]]

## 🤖 LLM 활용
**언제**: 매 frontend perf review. 매 SEO optimization. 매 React 19 migration.
**언제 X**: 매 backend (다른 metric).

## ❌ 안티패턴
- **CSR for SEO-critical**: 매 LCP 의 fail.
- **No image dimension**: 매 CLS 폭발.
- **Long synchronous task**: 매 INP fail.
- **Lab metric only**: 매 field 의 다름.
- **Virtualization X (long list)**: 매 INP / memory.
- **React.memo overuse**: 매 마이크로 optimization (no ROI).
- **No CI budget**: 매 regression 의 silent.

## 🧪 검증 / 중복
- Verified (web.dev Core Web Vitals, Google PageSpeed, React 19 docs).
- 신뢰도 A.
- Related: [[CSS Animations]] · [[Container_Queries]] · [[Baseline-Project]] · [[Case-Study-Allbirds-PWA-Redesign]] · [[Chrome DevTools 메모리 프로파일링]].

## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — LCP / INP / CLS + 매 web-vitals / RSC / virtualization / Image / Lighthouse code |
