Performance API — Navigation Timing, PerformanceObserver
웹 성능 최적화의 첫 단계는 "측정"입니다. Performance API는 페이지 로딩 시간, 리소스 로딩, 사용자 정의 마크/측정을 정밀하게 기록하는 브라우저 내장 API입니다.
performance.now() — 고정밀 시간 측정
const start = performance.now();
// 측정할 작업
heavyCalculation();
const end = performance.now();
console.log(`실행 시간: ${(end - start).toFixed(2)}ms`);
// Date.now()보다 정밀 (마이크로초 단위)
Mark와 Measure — 코드 구간 측정
// 마크 찍기
performance.mark("fetch-start");
const data = await fetch("/api/data").then((r) => r.json());
performance.mark("fetch-end");
// 구간 측정
performance.measure("fetch-duration", "fetch-start", "fetch-end");
// 결과 확인
const measures = performance.getEntriesByName("fetch-duration");
console.log(`API 호출: ${measures[0].duration.toFixed(2)}ms`);
// 정리
performance.clearMarks();
performance.clearMeasures();
Navigation Timing — 페이지 로드 성능
const timing = performance.getEntriesByType("navigation")[0];
// 주요 지표
console.log(`DNS 조회: ${timing.domainLookupEnd - timing.domainLookupStart}ms`);
console.log(`TCP 연결: ${timing.connectEnd - timing.connectStart}ms`);
console.log(`TTFB: ${timing.responseStart - timing.requestStart}ms`);
console.log(`DOM 파싱: ${timing.domContentLoadedEventEnd - timing.responseEnd}ms`);
console.log(`전체 로드: ${timing.loadEventEnd - timing.startTime}ms`);
console.log(`DOM Interactive: ${timing.domInteractive}ms`);
Resource Timing — 리소스별 로딩 시간
const resources = performance.getEntriesByType("resource");
resources.forEach((entry) => {
console.log(`${entry.name}: ${entry.duration.toFixed(2)}ms (${entry.initiatorType})`);
});
// 타입별 분류
const scripts = resources.filter((r) => r.initiatorType === "script");
const images = resources.filter((r) => r.initiatorType === "img");
const styles = resources.filter((r) => r.initiatorType === "link");
// 가장 느린 리소스 찾기
const slowest = resources.sort((a, b) => b.duration - a.duration)[0];
console.log(`가장 느린 리소스: ${slowest.name} (${slowest.duration}ms)`);
PerformanceObserver — 성능 이벤트 실시간 감시
// 새로운 성능 엔트리를 실시간으로 관찰
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`[${entry.entryType}] ${entry.name}: ${entry.duration}ms`);
}
});
// 관찰할 타입 지정
observer.observe({ entryTypes: ["measure", "resource", "longtask"] });
// Long Task 감지 (50ms 이상 걸리는 작업)
const longTaskObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.warn(`Long Task 감지: ${entry.duration.toFixed(2)}ms`);
}
});
longTaskObserver.observe({ entryTypes: ["longtask"] });
Core Web Vitals 측정
// LCP (Largest Contentful Paint)
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lcp = entries[entries.length - 1];
console.log(`LCP: ${lcp.startTime.toFixed(2)}ms`);
}).observe({ type: "largest-contentful-paint", buffered: true });
// FID (First Input Delay) → INP로 대체됨
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`FID: ${entry.processingStart - entry.startTime}ms`);
}
}).observe({ type: "first-input", buffered: true });
// CLS (Cumulative Layout Shift)
let clsValue = 0;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
}
console.log(`CLS: ${clsValue.toFixed(4)}`);
}).observe({ type: "layout-shift", buffered: true });
성능 데이터 전송
// 비콘으로 성능 데이터 전송 (페이지 언로드 시에도 안전)
function sendPerformanceData() {
const data = {
navigation: performance.getEntriesByType("navigation")[0]?.toJSON(),
resources: performance.getEntriesByType("resource").map((r) => ({
name: r.name,
duration: r.duration,
type: r.initiatorType,
})),
measures: performance.getEntriesByType("measure").map((m) => ({
name: m.name,
duration: m.duration,
})),
};
navigator.sendBeacon("/api/analytics", JSON.stringify(data));
}
window.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden") {
sendPerformanceData();
}
});
성능 지표 기준
| 지표 | 좋음 | 보통 | 나쁨 |
|---|---|---|---|
| LCP | < 2.5s | < 4s | > 4s |
| INP | < 200ms | < 500ms | > 500ms |
| CLS | < 0.1 | < 0.25 | > 0.25 |
| TTFB | < 800ms | < 1.8s | > 1.8s |
**기억하기 **:
performance.mark()와performance.measure()로 코드 구간의 실행 시간을 측정하고,PerformanceObserver로 Long Task와 Core Web Vitals를 실시간 감시합니다. 측정 없는 최적화는 추측일 뿐입니다.
댓글 로딩 중...