Skip to content

필사 모드: 현대 웹 성능의 과학 심화 가이드 — Core Web Vitals, INP, LCP, CLS, RUM, Lighthouse, Critical Rendering Path, Speculation Rules까지 (2025)

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

> **TL;DR** — 2024년은 웹 성능의 지각변동이었다. **INP(Interaction to Next Paint)**가 FID를 대체하며 Core Web Vitals 3대 지표가 **LCP · CLS · INP**로 재편됐고, **Speculation Rules API**(Chrome 121), **Partial Prerendering**(Next.js 14), **HTTP/3 QUIC**(전체 트래픽의 30% 돌파), **Early Hints**(103 Status)가 같은 해에 표준화·일반화됐다. 이 글은 '왜 내 사이트가 느린가'에 답하기 위해 **Critical Rendering Path** 원리부터 **Long Task 쪼개기**, **RUM vs Lab Data** 차이, **Lighthouse가 자주 틀리는 이유**, **Islands · Resumability**(Qwik), **Image/Font 최적화**(AVIF, font-display: optional), **2025년 성능 도구 스택**(Vercel Analytics, SpeedCurve, PerfEtto)까지 — 현대 웹 성능의 전 지형도를 정리한다.

왜 웹 성능이 다시 '뜨거운 주제'가 됐는가

웹 성능은 2010년대 초 **YSlow**(Yahoo)와 **PageSpeed Insights**(Google)가 대중화한 뒤 한동안 '인프라 팀의 체크리스트' 수준에 머물렀다. 그런데 2020년 Google이 **Core Web Vitals**를 검색 랭킹 신호로 공식 발표하고, 2024년 **INP**가 FID를 대체하며 — 성능은 **검색 순위 · 광고 전환율 · 사용자 이탈률**을 직접 좌우하는 비즈니스 지표가 됐다.

숫자로 보면 이렇다:

- **Amazon**: 페이지 로드 100ms 지연당 매출 1% 감소 (2006년 기준, 2024년엔 더 민감)

- **Walmart**: LCP 1초 단축 → 전환율 2% 향상

- **BBC**: 1초 지연당 10% 이탈 증가

- **Vodafone**: LCP 31% 개선 → 판매 전환 8% 증가 (2021 케이스스터디)

2024년 **Chrome UX Report**(CrUX, 실제 사용자 데이터)에 따르면, 전 세계 상위 100만 사이트 중 Core Web Vitals 3개를 모두 통과한 사이트는 **42%**뿐이다. 반면 React/Vue/Angular 같은 SPA 프레임워크를 쓰는 사이트의 통과율은 **28%**로 정적 사이트(65%)보다 크게 낮다. 성능은 '프레임워크 선택의 대가'이기도 하다.

이 글의 목적은 **Core Web Vitals 3개 지표의 정의와 측정법**, 그리고 **왜 같은 코드가 Lab에선 빠르고 Field에선 느린지**, **어떻게 Long Task와 Layout Shift를 추적·수정하는지**를 원리부터 실무까지 한 호흡에 정리하는 것이다. 성능은 '기법 하나'가 아니라 '**렌더링 파이프라인 전체의 이해**'에서 나온다.

브라우저 렌더링 파이프라인 — 픽셀까지 도달하는 여정

웹 성능을 논하려면 브라우저가 HTML을 받아 픽셀을 그리기까지 거치는 **Critical Rendering Path**를 알아야 한다. 이 경로에서 시간이 새는 모든 지점이 '성능 문제'의 원인이다.

1. Navigation — URL 입력 / 링크 클릭

2. DNS Lookup — example.com → 93.184.216.34

3. TCP + TLS Handshake — 3-way + SSL (HTTP/1.1: ~300ms, HTTP/3: ~100ms)

4. HTTP Request — GET /

5. TTFB — Time To First Byte (서버 응답)

6. HTML Parsing — DOM 트리 구축 (파싱 중 외부 리소스 요청)

7. CSSOM Construction — CSS 파싱, CSSOM 트리

8. Render Tree — DOM + CSSOM → 화면에 그릴 노드만

9. Layout (Reflow) — 각 노드의 위치/크기 계산

10. Paint — 픽셀 정보 생성 (레이어 단위)

11. Composite — GPU에서 레이어 합성

12. 화면 표시 — 사용자가 보는 최초 픽셀 (FCP)

각 단계에서 일어나는 핵심 병목:

1. **DNS + TCP + TLS** — 첫 바이트를 받기까지 RTT(Round Trip Time) 3-4회. 이걸 **HTTP/3 QUIC**(0-RTT resumption)이 공격한다.

2. **TTFB** — 서버 응답 시간. SSR이면 DB + 렌더링, 정적 파일이면 CDN 캐시 히트 여부가 결정.

3. **HTML Parsing Blocking** — `<script>` 태그는 기본적으로 파싱을 멈춘다. `async`/`defer`로 해소.

4. **CSSOM Blocking** — CSS는 **렌더링 블로킹 리소스**. CSS 로딩이 끝날 때까지 Render Tree 안 만들어짐.

5. **Layout** — `offsetHeight` 읽기 같은 **동기 강제 레이아웃**(Forced Reflow)이 성능 킬러.

6. **Paint / Composite** — `will-change: transform`, `contain: layout`으로 GPU 레이어 분리 유도.

React/Vue 같은 JS 프레임워크를 쓰면 여기에 **JS 다운로드 · 파싱 · 실행 · Hydration**이 추가된다. 이 추가 단계가 SPA가 Core Web Vitals에서 불리한 근본 이유다.

Core Web Vitals 3대 지표 (2024-2025)

Google은 2020년 **Core Web Vitals**를 사용자 경험의 3대 축으로 정의했다:

- **로딩 (Loading)** — LCP

- **상호작용성 (Interactivity)** — FID → **INP** (2024.03 교체)

- **시각적 안정성 (Visual Stability)** — CLS

LCP — Largest Contentful Paint (로딩)

**정의**: 뷰포트에 보이는 가장 큰 콘텐츠 요소(이미지, 비디오 포스터, 블록 텍스트)가 화면에 그려지는 시점.

**기준**: 2.5초 이하 = Good, 2.5-4.0초 = Needs Improvement, 4.0초 초과 = Poor.

**측정**: `LargestContentfulPaint` PerformanceObserver API.

new PerformanceObserver((list) => {

for (const entry of list.getEntries()) {

console.log('LCP element:', entry.element)

console.log('LCP time:', entry.startTime)

console.log('LCP render time:', entry.renderTime)

console.log('LCP size:', entry.size)

}

}).observe({ type: 'largest-contentful-paint', buffered: true })

**LCP가 느려지는 주범 4가지**:

1. **느린 TTFB** — 서버 응답이 1초 걸리면 LCP 2.5초 통과는 불가능.

2. **렌더링 블로킹 리소스** — `<link rel="stylesheet">` 지연이 LCP를 그대로 지연시킴.

3. **리소스 로드 시간** — LCP 이미지가 lazy-load되면 안 됨. `fetchpriority="high"` 사용.

4. **클라이언트 사이드 렌더링** — React가 JS 다운받아 Hydration 끝나야 LCP 요소가 DOM에 올라옴.

**LCP 최적화 체크리스트**:

- LCP 이미지에 `fetchpriority="high"` + `loading="eager"` + `decoding="async"`

- `<link rel="preload" as="image" href="/hero.webp" imagesrcset="..." fetchpriority="high">`

- Above-the-fold 콘텐츠는 인라인 CSS로

- 폰트는 `font-display: optional` 또는 `swap`

- CDN + HTTP/3 + Brotli 압축

- SSR 또는 SSG (CSR 피하기)

CLS — Cumulative Layout Shift (시각적 안정성)

**정의**: 페이지 로드 중 예기치 않게 레이아웃이 이동한 정도. 이동한 요소의 '영향 비율' × '이동 거리'를 누적.

**기준**: 0.1 이하 = Good, 0.1-0.25 = Needs Improvement, 0.25 초과 = Poor.

**CLS 공식**: `impact fraction × distance fraction`

- Impact Fraction: 뷰포트 대비 이동한 요소가 차지하는 비율

- Distance Fraction: 이동한 거리 / 뷰포트 크기

**CLS 주범 5가지**:

1. **크기 지정 없는 이미지** — `<img>`에 width/height 누락 → 이미지 로드 후 레이아웃 밀림.

2. **크기 지정 없는 광고/임베드** — AdSense, YouTube embed, iframe.

3. **FOIT/FOUT** — 폰트 로드 전후 글꼴 변경으로 텍스트 높이 변화.

4. **동적 콘텐츠 주입** — 상단에 배너/알림 삽입.

5. **Web Font 로드 지연** — `font-display: swap`이 CLS를 일으킴 (아이러니).

**CLS 최적화**:

<!-- 이미지 크기 명시 -->

<!-- CSS aspect-ratio로 공간 예약 -->

.embed { aspect-ratio: 16 / 9; }

<!-- Font fallback 크기 조정 -->

@font-face {

font-family: 'Inter';

src: url('inter.woff2') format('woff2');

font-display: optional; /* CLS 방지, 폰트 로드 안 되면 fallback 유지 */

size-adjust: 107%; /* fallback과 크기 일치시키기 */

}

<!-- Skeleton / Placeholder -->

INP — Interaction to Next Paint (2024.03 공식화)

**정의**: 사용자가 클릭/탭/키보드 입력을 했을 때, 다음 프레임이 그려지기까지의 시간 — **페이지 전체 수명 동안 최악(가장 느린) 상호작용** 기준(98 percentile 가까움).

**기준**: 200ms 이하 = Good, 200-500ms = Needs Improvement, 500ms 초과 = Poor.

**INP가 FID를 왜 대체했나**:

- **FID(First Input Delay)**는 첫 입력의 '시작 지연'만 측정 (입력 → 핸들러 시작).

- 하지만 실제 UX 문제는 **입력 후 → 화면 반영까지** 전체 시간. 긴 JS 작업, 리렌더링, Layout, Paint 모두 포함해야 함.

- FID는 대부분 사이트가 100ms 이내 통과 → 변별력 없음. INP는 훨씬 엄격.

**INP 공식** (간소화):

INP = max(interactions) where interaction_time =

(input delay) + (processing time) + (presentation delay)

**INP 측정**:

onINP((metric) => {

console.log('INP:', metric.value, 'ms')

console.log('Attribution:', metric.attribution)

// attribution: { interactionType, eventTarget, loafTime, ... }

})

**INP가 나빠지는 주범 7가지**:

1. **Long Task** — 50ms 초과 메인 스레드 블로킹 JS.

2. **큰 React 컴포넌트 리렌더** — state 업데이트 → 전체 subtree 재렌더.

3. **동기 네트워크 요청** — fetch를 event handler에서 await.

4. **대규모 DOM** — 수천 개 노드의 Layout 재계산.

5. **heavy CSS selector** — `:has()`, 복잡한 nth-child.

6. **동기 third-party 스크립트** — 광고, 분석 도구.

7. **ResizeObserver/MutationObserver 폭주** — 콜백이 동기 Layout 유발.

**INP 최적화 — Long Task 쪼개기**:

// 나쁨 — 1000개 아이템을 한 번에 처리 (800ms Long Task)

function processItems(items) {

items.forEach(item => expensiveWork(item))

}

// 좋음 — scheduler.yield() (2024 표준화)

async function processItems(items) {

for (const item of items) {

expensiveWork(item)

await scheduler.yield() // 메인 스레드 양보

}

}

// 대체 — setTimeout으로 양보 (구형 브라우저)

function processItemsYield(items, i = 0) {

const deadline = performance.now() + 10

while (i < items.length && performance.now() < deadline) {

expensiveWork(items[i++])

}

if (i < items.length) {

setTimeout(() => processItemsYield(items, i), 0)

}

}

**React INP 특화 — useTransition**:

function SearchBox() {

const [isPending, startTransition] = useTransition()

const [query, setQuery] = useState('')

const [results, setResults] = useState([])

function handleChange(e) {

setQuery(e.target.value) // 긴급 업데이트 (input value)

startTransition(() => {

setResults(expensiveSearch(e.target.value)) // 낮은 우선순위

})

}

// ...

}

RUM vs Lab Data — 왜 Lighthouse는 자주 틀리는가

웹 성능 데이터에는 크게 두 종류가 있다:

Lab Data (합성 모니터링, Synthetic)

- **Lighthouse**, **WebPageTest**, **PageSpeed Insights**(Lab 탭)

- 통제된 환경에서 1회 측정

- 장점: 재현 가능, 회귀 탐지 쉬움, CI/CD에 통합 가능

- 단점: 실제 사용자 네트워크/디바이스/상호작용과 다름

Field Data / RUM (Real User Monitoring)

- **Chrome UX Report**(CrUX), **Vercel Analytics**, **Sentry Performance**, **New Relic Browser**, **SpeedCurve**

- 실제 사용자 브라우저에서 Performance API로 수집

- 장점: 실제 UX 반영, 디바이스/네트워크 분포 반영

- 단점: 노이즈 많음, 디버깅 어려움 (어떤 상호작용이 INP 80ms인지 추적 필요)

**왜 Lighthouse 점수와 실제 점수가 다른가**:

1. Lighthouse는 **Moto G Power + 4G 시뮬레이션** 고정. 실제 유저는 iPhone 15 + 5G일 수도.

2. Lighthouse는 페이지 로드만 측정 → **INP는 세션 전체 상호작용 기반** → Lab에선 측정 안 됨.

3. Lighthouse는 **1회 측정** → 실제는 분포. Google은 CrUX의 **75 percentile**을 기준 삼음.

4. Lighthouse는 **쿠키/로그인 없음** → 실제는 인증된 사용자 화면이 다름.

5. Lighthouse는 **viewport 고정 360×640** → 실제 디바이스 폭 분포와 다름.

**권장 전략**:

- **Lab Data (Lighthouse)**: PR마다 회귀 테스트 (Lighthouse CI), 상한선 설정

- **RUM**: 프로덕션 모니터링, p75 / p95 지표 추적, 국가별/디바이스별 드릴다운

- 두 데이터가 일치하지 않을 때 **RUM을 믿어라**

2025년 RUM 스택

| 제공자 | 특징 | 가격(참고) |

|---|---|---|

| **Vercel Speed Insights** | Next.js 통합, Core Web Vitals + Custom Events | 10,000 events 무료 |

| **Google CrUX** | 월별 공개 데이터, BigQuery | 무료 |

| **Sentry Performance** | 에러 + 성능 통합 | $26/mo부터 |

| **SpeedCurve** | 경쟁사 비교, 커스텀 대시보드 | $149/mo부터 |

| **New Relic Browser** | APM 통합 | Free tier |

| **Cloudflare Web Analytics** | 서버리스, 프라이버시 우선 | 무료 |

| **Pingdom RUM** | 지리적 분포 강점 | $14.95/mo부터 |

리소스 우선순위와 로딩 전략

브라우저가 100개 리소스를 동시에 받는 건 아니다. **Fetch Priority**, **Preload Scanner**, **HTTP/2 Priority**, **HTTP/3 Priority**가 복잡하게 얽혀 결정한다.

Resource Hints — 브라우저에 미리 알리기

<!-- DNS 미리 해석 -->

<!-- 연결(DNS + TCP + TLS) 미리 열기 -->

<!-- 리소스 미리 다운로드 (현재 페이지용) -->

<!-- 다음 페이지 미리 가져오기 (low priority) -->

<!-- 전체 페이지 미리 렌더링 (Speculation Rules로 대체됨) -->

Fetch Priority API (Chrome 101+, Safari 17+, Firefox 132+)

<!-- LCP 이미지 -->

<!-- 아래 스크롤 영역 이미지 -->

<!-- fetch() API -->

fetch('/critical.json', { priority: 'high' })

fetch('/analytics.json', { priority: 'low' })

Speculation Rules API — 2024 표준화

기존 `<link rel="prefetch">`와 `prerender`의 한계를 넘어, **CSS selector 기반**으로 사용자가 **방문할 가능성이 높은 링크**를 미리 prerender.

{

"prerender": [{

"urls": ["/product/1", "/product/2"],

"eagerness": "moderate"

}],

"prefetch": [{

"where": { "href_matches": "/product/*" },

"eagerness": "conservative"

}]

}

**eagerness 레벨**:

- `immediate` — 즉시 (aggressive)

- `eager` — 힌트 발견 즉시

- `moderate` — 링크에 hover/touch 등

- `conservative` — 링크 클릭 직전

Chrome 121+, **실질적 LCP 0ms 달성**이 가능. (prerender된 페이지 전환 시 즉시 표시)

Early Hints (HTTP 103)

서버가 최종 응답(200) 전에 **103 Early Hints** 상태코드로 `Link: </main.css>; rel=preload` 힌트를 먼저 보내는 기술.

HTTP/1.1 103 Early Hints

Link: </main.css>; rel=preload; as=style

Link: </hero.webp>; rel=preload; as=image

HTTP/1.1 200 OK

Content-Type: text/html

...

Cloudflare, Fastly, Next.js(14.1+)가 지원. TTFB를 기다리지 않고 핵심 리소스를 미리 받음 → LCP 200-400ms 단축 가능.

이미지 최적화 — 모든 웹사이트 대역폭의 50%

Chrome UX Report에 따르면 평균 웹페이지의 **이미지는 전체 바이트의 48%**. 이미지 최적화 하나로 LCP를 1초 이상 단축할 수 있다.

포맷 선택

| 포맷 | 지원 | 특징 | 압축률 |

|---|---|---|---|

| **JPEG** | 100% | 사진, 손실 압축 | 기준 |

| **PNG** | 100% | 투명도, 무손실 | 크기 큼 |

| **WebP** | 97% (IE 제외) | Google, 25-35% 작음 | JPEG 대비 25-35% 작음 |

| **AVIF** | 93% | AV1 코덱 기반, 50% 작음 | JPEG 대비 50% 작음 |

| **JPEG XL** | Safari only (실험적) | 미래 표준 후보 | AVIF와 비슷 |

**2025년 권장**: `<picture>`로 AVIF → WebP → JPEG fallback.

Responsive Images — srcset + sizes

src="/hero-800.jpg"

srcset="/hero-400.jpg 400w,

/hero-800.jpg 800w,

/hero-1600.jpg 1600w,

/hero-2400.jpg 2400w"

sizes="(max-width: 768px) 100vw,

(max-width: 1200px) 50vw,

33vw"

width="800" height="600"

alt="Hero"

/>

현대 CDN — 자동 포맷 변환

- **Cloudinary** — URL 기반 변환 (`w_800,f_auto,q_auto`)

- **imgix** — 동적 파라미터

- **Cloudflare Images** — $5/월 100k 이미지

- **Next.js Image** — `<Image />` 컴포넌트 (AVIF/WebP 자동)

- **Vercel Image Optimization** — 빌드 타임 + 온디맨드

Lazy Loading

<!-- 기본 lazy loading (Chrome 77+) -->

<!-- IntersectionObserver 기반 커스텀 -->

const images = document.querySelectorAll('img[data-src]')

const observer = new IntersectionObserver((entries) => {

entries.forEach(entry => {

if (entry.isIntersecting) {

entry.target.src = entry.target.dataset.src

observer.unobserve(entry.target)

}

})

}, { rootMargin: '200px' }) // 200px 전에 미리 로드

images.forEach(img => observer.observe(img))

**주의**: LCP 이미지는 **절대 lazy loading 하지 말 것**. `loading="eager"` + `fetchpriority="high"`.

폰트 최적화 — FOIT/FOUT와 CLS의 주범

웹 폰트는 다운로드 전까지 텍스트가 안 보이거나(FOIT), fallback으로 보이다 갑자기 바뀐다(FOUT) — 모두 사용자 경험을 해친다.

font-display 전략

@font-face {

font-family: 'Inter';

src: url('inter.woff2') format('woff2');

font-display: swap; /* FOUT: fallback 보여주다 폰트 로드되면 교체 — CLS 발생 */

font-display: optional; /* 100ms 안에 안 오면 fallback 유지 — CLS 0 */

font-display: block; /* 3초까지 기다림 — FOIT */

font-display: fallback; /* 100ms + 3초 */

}

**권장**: LCP 텍스트는 `font-display: optional` + **size-adjust**로 fallback 크기 맞추기.

Font Fallback 크기 맞추기 (size-adjust)

@font-face {

font-family: 'Inter';

src: url('inter.woff2') format('woff2');

font-display: optional;

}

@font-face {

font-family: 'Inter-fallback';

src: local('Arial');

size-adjust: 107.4%; /* Arial을 Inter 크기로 조정 */

ascent-override: 90%;

descent-override: 22%;

line-gap-override: 0%;

}

body {

font-family: 'Inter', 'Inter-fallback', sans-serif;

}

**도구**: [Font Style Matcher](https://meowni.ca/font-style-matcher/), **Fontaine**(Vite plugin).

Preload + Subset

<!-- 필수 폰트 preload -->

**Subset**: 한국어 폰트(Noto Sans KR, Pretendard)는 전체 글리프가 3-10MB. **unicode-range**로 한국어 영역만 로드.

@font-face {

font-family: 'Pretendard';

src: url('Pretendard-KR.woff2') format('woff2');

unicode-range: U+AC00-D7A3, U+1100-11FF, U+3130-318F; /* 한글 영역만 */

}

JavaScript 로딩 전략

JS는 웹 성능의 가장 큰 적이자 친구다. 현대 SPA는 평균 **400KB gzipped JS**를 로드 — 모바일에서 파싱/컴파일만 800ms+.

async vs defer vs module

<!-- Blocking (절대 쓰지 말 것) -->

<!-- Async: 다운로드 완료 즉시 실행, 순서 보장 X -->

<!-- Defer: 다운로드 병렬, HTML 파싱 끝나고 실행, 순서 보장 -->

<!-- Module: 기본 defer, 순서 보장 -->

Code Splitting

Webpack/Rollup/esbuild 모두 지원. 라우트/컴포넌트 단위로 JS를 쪼개 초기 로드량 감소.

// React.lazy

const Heavy = React.lazy(() => import('./HeavyComponent'))

function App() {

return (

)

}

// Next.js dynamic

const Chart = dynamic(() => import('./Chart'), { ssr: false })

Tree Shaking

ESM import 구문을 분석해 사용 안 한 코드를 제거. **side-effect free** 선언 필수.

// package.json

{

"sideEffects": false,

"exports": {

".": "./dist/index.js"

}

}

Third-Party Scripts — 가장 큰 적

Google Analytics, Facebook Pixel, Intercom 등 3rd party 스크립트 하나가 INP 500ms를 만든다.

**Partytown** (Builder.io) — 3rd party 스크립트를 **Web Worker**에서 실행.

**Next.js Script 컴포넌트**:

Long Task와 메인 스레드 예산

**Long Task**: 50ms 초과로 메인 스레드를 블로킹하는 JS 작업. INP를 망치는 최대 원인.

Long Task 감지

new PerformanceObserver((list) => {

for (const entry of list.getEntries()) {

console.warn('Long Task:', entry.duration, 'ms', entry.attribution)

}

}).observe({ type: 'longtask', buffered: true })

Long Animation Frames (LoAF) — 2024 신규

Long Task의 한계를 보완한 새 API. 프레임 단위로 **렌더링 + 스크립트 + Layout + Paint** 시간 분석.

new PerformanceObserver((list) => {

for (const entry of list.getEntries()) {

console.log('LoAF:', {

duration: entry.duration,

scripts: entry.scripts, // 어떤 스크립트가 얼마나 걸렸나

blockingDuration: entry.blockingDuration,

})

}

}).observe({ type: 'long-animation-frame', buffered: true })

scheduler.yield() — 메인 스레드 양보

async function bulkWork(items) {

for (const item of items) {

process(item)

if (navigator.scheduling?.isInputPending()) {

await scheduler.yield() // 입력 대기 중이면 즉시 양보

}

}

}

Web Worker — CPU 오프로딩

// main.js

const worker = new Worker('/worker.js')

worker.postMessage({ cmd: 'hash', data: largeString })

worker.onmessage = (e) => console.log('hashed:', e.data)

// worker.js

self.onmessage = async (e) => {

const buf = new TextEncoder().encode(e.data.data)

const hash = await crypto.subtle.digest('SHA-256', buf)

self.postMessage(Array.from(new Uint8Array(hash)))

}

Hydration 문제 — SPA의 근본 비용

React/Vue/Angular 같은 SPA는 **Hydration** — 서버에서 렌더된 HTML에 JS를 붙여 상호작용 가능하게 만드는 과정 — 이 INP를 망친다.

Hydration의 6단계 비용 (Addy Osmani)

1. JS 다운로드 — 200-500KB gzipped

2. JS 파싱 + 컴파일

3. React Tree 재구성 (서버 HTML 무관)

4. 이벤트 리스너 부착

5. useState/useEffect 실행

6. Commit

모바일에서 이 전체가 **1-3초** 걸린다. 그동안 사용자 클릭은 무시.

해결책 1: Partial Hydration (Islands)

**Astro**, **Marko**, **Fresh**(Deno)의 접근. 페이지 대부분은 정적 HTML, **상호작용이 필요한 부분만** 섬(island)으로 Hydrate.

// Astro 파일

해결책 2: Resumability (Qwik)

Qwik의 혁신: **Hydration 자체를 없애고**, HTML에 serialize된 상태를 사용자 상호작용 시점에 재개(resume).

// Qwik 컴포넌트

export default component$(() => {

const count = useSignal(0)

return (

{count.value}

)

})

HTML에 핸들러 URL을 임베드:

**JS 0KB로 시작**, 클릭 시점에만 해당 핸들러 JS를 lazy load. **TTI = LCP**를 실현.

해결책 3: React Server Components + Streaming

React 18 + Next.js 14. 서버 컴포넌트는 JS 번들에 포함 안 됨 → 클라이언트 번들 감소. Streaming으로 `<Suspense>` 경계까지 먼저 렌더 → LCP 단축.

해결책 4: Selective Hydration

React 18 기본 기능. `<Suspense>` 경계를 우선순위 기반으로 Hydrate. 사용자가 클릭한 영역 우선 처리.

HTTP/3, QUIC, 그리고 네트워크 계층

2024년 HTTP/3가 전체 트래픽의 **30%**를 넘어섰다(W3Techs). TCP 위에서 동작하는 HTTP/2와 달리, HTTP/3는 **UDP 기반 QUIC 프로토콜** 위에서 동작한다.

HTTP/1.1 → HTTP/2 → HTTP/3

| 버전 | 기반 | 멀티플렉싱 | Head-of-Line Blocking | 0-RTT |

|---|---|---|---|---|

| HTTP/1.1 | TCP | X (6 connections/origin) | Yes | No |

| HTTP/2 | TCP | Yes | **TCP 수준** Yes | No |

| HTTP/3 | UDP (QUIC) | Yes | No | **Yes** |

**HTTP/3 핵심 이득**:

1. **0-RTT Resumption** — 이전 연결 키 재사용, 첫 요청부터 데이터 전송

2. **Connection Migration** — WiFi → 셀룰러 전환해도 연결 유지 (Connection ID)

3. **HOL Blocking 해소** — 한 스트림 패킷 손실이 다른 스트림 차단 안 함

4. **암호화 필수** — TLS 1.3 내장, 평문 불가

실제 측정 (Cloudflare 2024 자료):

- Google Search: HTTP/3로 중앙값 응답 시간 3% 단축, 상위 10% 구간 10% 단축

- Facebook: 동영상 rebuffering 20% 감소

- Akamai: TTFB 모바일 12% 개선

CDN + Edge 컴퓨팅

**Cloudflare**, **Fastly**, **AWS CloudFront**, **Vercel Edge Network**, **Bunny.net**. 콘텐츠를 사용자 가까이 캐시해 지연 최소화.

**2025년 트렌드**:

- **Edge Workers** — Cloudflare Workers, Deno Deploy, Vercel Edge Functions. V8 Isolate 기반 수 ms 콜드 스타트.

- **Regional Edge Cache** — 기존 Origin → Edge 2단계를 3단계로 (Origin → Regional → Edge).

- **Smart Placement** (Cloudflare) — 사용자 기반이 아닌 **Origin 가까이** 배치해 DB 지연 최소화.

2025년 성능 도구 스택

측정 도구

- **Chrome DevTools Performance** — 가장 기본. 2024년 **Performance Insights** 패널 추가로 Core Web Vitals 실시간 분석.

- **Lighthouse** — Chrome 내장, CI용 `lighthouse-ci`, Vercel/Netlify 통합.

- **WebPageTest** — 상세 분석, Filmstrip, 연결 상세. 무료 + 유료 플랜.

- **PageSpeed Insights** — Lab(Lighthouse) + Field(CrUX) 통합 뷰.

- **Chrome UX Report** — 월간 공개, BigQuery로 경쟁사 비교.

프로파일러

- **SpeedScope** — https://www.speedscope.app, 불꽃 그래프 시각화, Chrome Performance 프로파일 import.

- **Perfetto** — Chrome DevTools와 Chromium 내부 추적, UI 공유.

- **React DevTools Profiler** — 컴포넌트 렌더 시간 분해.

- **Next.js Build Analyzer** — `@next/bundle-analyzer`, 번들 크기 시각화.

RUM

- **Vercel Speed Insights** + **Web Analytics** — Next.js 기본.

- **Sentry Performance** — 에러 + RUM 통합.

- **New Relic Browser** — APM 연동.

- **Cloudflare Web Analytics** — 무료, 프라이버시 우선.

- **SpeedCurve** — 경쟁사 비교에 강점.

최적화 도구

- **Next.js Image** + **Vercel Image Optimization** — AVIF/WebP 자동.

- **Sharp** (Node.js) — 서버 사이드 이미지 변환.

- **Partytown** — 3rd party 스크립트 Worker 격리.

- **Fontaine** (Vite plugin) — fallback 폰트 자동 생성.

- **Critical** (Addy Osmani) — Critical CSS 추출.

실전 최적화 체크리스트 (2025)

실제 웹사이트를 최적화할 때 순서:

1. **RUM 도입** — Vercel Speed Insights 또는 web-vitals 라이브러리로 실제 지표 측정

2. **CDN + HTTP/3 + Brotli** — 네트워크 계층

3. **서버 TTFB 200ms 이하** — DB 쿼리 최적화, SSR 캐싱, Edge Function

4. **LCP 이미지 최적화** — AVIF + `fetchpriority="high"` + `preload`

5. **Critical CSS 인라인 + 나머지 deferred** — `media="print"` 해킹 또는 Critical 라이브러리

6. **Font Loading** — `font-display: optional` + size-adjust fallback

7. **JS Code Splitting** — 라우트 단위 + React.lazy

8. **3rd Party Scripts 감사** — Partytown, `next/script` strategy="lazyOnload"

9. **CLS 제거** — 이미지/iframe width/height, Ad 슬롯 aspect-ratio, 폰트 fallback 매칭

10. **INP 최적화** — Long Task 쪼개기 (scheduler.yield), useTransition, Web Worker

11. **Speculation Rules** — 예측 가능한 다음 페이지 prerender

12. **회귀 방지** — Lighthouse CI, Performance Budget (webpack/rollup plugin)

10가지 흔한 안티패턴

1. **LCP 이미지에 `loading="lazy"` 적용** — LCP 영구 지연.

2. **Fonts 없이 커스텀 폰트 사용** — FOIT 3초, 빈 화면.

3. **React 전체 Hydration + 정적 사이트** — Astro/Next SSG를 안 쓰고 Next.js CSR.

4. **Third-party 스크립트 blocking 로드** — GA/GTM을 head에 그냥 넣기.

5. **클라이언트에서 Markdown 렌더링** — 서버에서 미리 HTML로 변환해야.

6. **Lighthouse 점수만 모니터링** — RUM 없이 실제 사용자 경험 맹목.

7. **Layout Thrashing** — `for` 루프 안에서 `offsetHeight` 반복 읽고 쓰기.

8. **거대 이미지 원본 로드** — 4K 이미지를 200px 썸네일에 사용.

9. **동기 `fetch`를 event handler에 await** — INP 크게 악화.

10. **Hydration 중 state 업데이트** — 무한 Hydration/Rerender 루프.

다음 글 예고 — 데이터베이스의 새 물결 — PostgreSQL, pgvector, pg_vector, HNSW, AI 시대의 DB 전략

웹 성능 최적화의 종착지는 보통 **데이터베이스**다. 아무리 CDN 잘 써도 DB 쿼리가 느리면 TTFB가 망가진다. 2023-2025년 데이터베이스 세계의 가장 큰 사건은 **PostgreSQL의 벡터 데이터베이스 정복**이었다. **pgvector** 확장이 Pinecone, Weaviate, Qdrant 같은 전용 벡터 DB를 위협하며 '만능 DB로서의 PostgreSQL' 시대를 열었다.

다음 글에서는:

- **PostgreSQL 왜 다시 1등인가** — StackOverflow 2024 개발자 설문 1위

- **pgvector와 HNSW 인덱스** — 벡터 검색의 수학과 실제

- **pgvector vs Pinecone vs Weaviate vs Qdrant** — 성능/기능/비용 비교

- **PostgreSQL 17의 비약** — Logical Replication, Incremental Backup

- **Supabase, Neon, PlanetScale, CockroachDB** — 클라우드 PostgreSQL 생태계

- **JSON, JSONB, GIN index** — NoSQL 기능 완벽 통합

- **MVCC 원리** — 낙관적 동시성의 우아함

- **Citus, TimescaleDB, PostGIS** — 확장 생태계

- **PostgreSQL + AI** — RAG 파이프라인 실전

...을 다룬다. '**하나의 DB로 모든 것**'이 현실이 된 시대, 그 배경과 실전 설계를 살펴본다. 웹 성능의 여정이 데이터 계층으로 이어지는 이유를 추적해보자.

현재 단락 (1/439)

웹 성능은 2010년대 초 **YSlow**(Yahoo)와 **PageSpeed Insights**(Google)가 대중화한 뒤 한동안 '인프라 팀의 체크리스트' 수준에 머물렀다....

작성 글자: 0원문 글자: 17,366작성 단락: 0/439