웹 성능이 비즈니스에 미치는 영향
페이지 로딩이 1초 지연되면 전환율이 7% 감소합니다. 웹 성능은 기술적 문제가 아니라 비즈니스 문제입니다. 식당에 들어갔는데 주문 후 30분을 기다려야 한다면, 대부분의 고객은 나가버립니다. 웹사이트도 마찬가지입니다.
Google은 2021년부터 Core Web Vitals를 검색 순위에 반영하고 있습니다. 성능이 좋은 사이트가 SEO에서도 유리합니다.
Core Web Vitals 이해
| 지표 | 측정 대상 | 좋음 | 개선 필요 | 나쁨 |
|---|---|---|---|---|
| LCP (Largest Contentful Paint) | 가장 큰 콘텐츠 렌더링 시간 | 2.5초 이하 | 2.5~4초 | 4초 초과 |
| INP (Interaction to Next Paint) | 사용자 인터랙션 응답 시간 | 200ms 이하 | 200~500ms | 500ms 초과 |
| CLS (Cumulative Layout Shift) | 시각적 안정성 (레이아웃 이동) | 0.1 이하 | 0.1~0.25 | 0.25 초과 |
측정 도구
# Lighthouse CLI로 성능 측정
npm install -g lighthouse
lighthouse https://example.com --output html --output-path ./report.html
# PageSpeed Insights API 활용
curl "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?\
url=https://example.com&\
category=performance&\
strategy=mobile"
LCP 개선: 가장 큰 콘텐츠를 빠르게
LCP는 뷰포트에서 가장 큰 이미지나 텍스트 블록이 렌더링되는 시간입니다.
주요 원인과 해결 방법
<!-- 1. 히어로 이미지 최적화 — LCP 요소를 빠르게 로드 -->
<!-- 잘못됨: lazy loading이 LCP 요소에 적용 -->
<img src="hero.jpg" loading="lazy" alt="메인 배너" />
<!-- 올바름: LCP 요소는 즉시 로드 + preload -->
<link rel="preload" as="image" href="hero.webp" />
<img src="hero.webp"
fetchpriority="high"
alt="메인 배너"
width="1200"
height="600" />
<!-- 2. 크리티컬 CSS 인라인 삽입 -->
<head>
<!-- 첫 화면에 필요한 CSS를 인라인으로 -->
<style>
/* 크리티컬 CSS — 첫 화면 렌더링에 필수적인 스타일만 */
.hero { display: flex; align-items: center; min-height: 60vh; }
.hero-title { font-size: 2.5rem; font-weight: 700; }
.nav { display: flex; gap: 1rem; padding: 1rem; }
</style>
<!-- 나머지 CSS는 비동기 로드 -->
<link rel="preload" as="style" href="/styles/main.css"
onload="this.onload=null;this.rel='stylesheet'" />
</head>
// 3. 서버 응답 시간 최적화 (TTFB 개선)
// Express.js에서 응답 압축 + 캐시 헤더 설정
import express from "express";
import compression from "compression";
const app = express();
// Gzip/Brotli 압축 활성화
app.use(compression({
level: 6, // 압축 레벨 (1-9, 6이 균형)
threshold: 1024 // 1KB 이상만 압축
}));
// 정적 파일 캐시 헤더 설정
app.use("/static", express.static("public", {
maxAge: "30d", // 30일 캐시
immutable: true, // 파일명에 해시가 포함된 경우
etag: true
}));
// HTML은 항상 최신 버전 제공
app.use((req, res, next) => {
if (req.path.endsWith(".html") || !req.path.includes(".")) {
res.setHeader("Cache-Control", "no-cache, must-revalidate");
}
next();
});
CLS 개선: 레이아웃 이동 방지
CLS는 페이지 로딩 중 요소가 갑자기 이동하는 현상입니다. 사용자가 클릭하려는 순간 버튼이 아래로 밀리는 경험은 매우 불쾌합니다.
<!-- 1. 이미지/비디오에 크기 명시 — 공간 미리 확보 -->
<!-- 잘못됨: 크기 없음 → 이미지 로드 후 레이아웃 밀림 -->
<img src="photo.webp" alt="사진" />
<!-- 올바름: width/height 또는 aspect-ratio 설정 -->
<img src="photo.webp" alt="사진" width="800" height="600" />
<!-- CSS aspect-ratio 활용 -->
<style>
.video-container {
aspect-ratio: 16 / 9;
width: 100%;
background: #f0f0f0; /* 로딩 중 플레이스홀더 */
}
</style>
/* 2. 웹폰트 로딩 시 레이아웃 이동 방지 */
/* font-display: swap — 폰트 로드 전 시스템 폰트 표시 */
@font-face {
font-family: "Pretendard";
src: url("/fonts/Pretendard.woff2") format("woff2");
font-display: swap; /* FOUT 허용, CLS 방지 */
size-adjust: 100%; /* 폰트 교체 시 크기 차이 최소화 */
}
/* 3. 동적 콘텐츠의 공간 미리 확보 */
.ad-slot {
min-height: 250px; /* 광고 영역 미리 확보 */
background: #f5f5f5;
contain: layout; /* CSS containment로 격리 */
}
.skeleton-card {
height: 200px; /* 스켈레톤 UI로 공간 확보 */
border-radius: 8px;
background: linear-gradient(
90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%
);
animation: shimmer 1.5s infinite;
}
이미지 최적화
이미지는 평균적으로 페이지 전체 용량의 50% 이상을 차지합니다.
<!-- 1. 차세대 포맷 사용 + 폴백 -->
<picture>
<!-- AVIF — 최고 압축률 (Chrome 85+, Firefox 93+) -->
<source type="image/avif" srcset="photo.avif" />
<!-- WebP — 넓은 호환성 (IE 제외 전체) -->
<source type="image/webp" srcset="photo.webp" />
<!-- JPEG — 폴백 -->
<img src="photo.jpg" alt="사진" width="800" height="600"
loading="lazy" decoding="async" />
</picture>
<!-- 2. 반응형 이미지 — 화면 크기에 맞는 이미지 제공 -->
<img srcset="photo-400w.webp 400w,
photo-800w.webp 800w,
photo-1200w.webp 1200w"
sizes="(max-width: 600px) 400px,
(max-width: 1024px) 800px,
1200px"
src="photo-800w.webp"
alt="반응형 이미지"
loading="lazy"
decoding="async" />
| 포맷 | 압축률 (vs JPEG) | 브라우저 지원 | 용도 |
|---|---|---|---|
| AVIF | 50% 감소 | 모던 브라우저 | 사진, 일러스트 |
| WebP | 30% 감소 | IE 제외 전체 | 범용 |
| JPEG | 기준 | 전체 | 폴백 |
| PNG | 용량 큼 | 전체 | 투명도 필요 시 |
| SVG | 매우 작음 | 전체 | 아이콘, 로고 |
브라우저 캐싱 전략
# Nginx 캐싱 설정 예시
# 정적 에셋 — 장기 캐시 (파일명에 해시 포함 전제)
location ~* \.(js|css|woff2|avif|webp|png|jpg|svg)$ {
expires 365d;
add_header Cache-Control "public, immutable";
}
# HTML — 항상 재검증
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-cache, must-revalidate";
}
# API 응답 — 캐시 금지
location /api/ {
add_header Cache-Control "no-store";
}
| 전략 | Cache-Control | 대상 | 설명 |
|---|---|---|---|
| 장기 캐시 | max-age=31536000, immutable | JS, CSS, 이미지 (해시 포함) | 파일명 변경으로 캐시 무효화 |
| 재검증 | no-cache | HTML | 매번 서버에 확인 후 사용 |
| 캐시 금지 | no-store | API, 개인 데이터 | 절대 캐시하지 않음 |
| 짧은 캐시 | max-age=300 | 자주 변경되는 데이터 | 5분간 캐시 |
실전 팁
- 측정 먼저, 최적화 나중: 감으로 최적화하지 마세요. Lighthouse, WebPageTest로 병목 지점을 정확히 파악한 후 개선하세요.
- 가장 큰 효과부터 적용하세요: 이미지 최적화(WebP 전환)와 캐싱 설정만으로도 체감 성능이 크게 개선됩니다.
- 번들 크기를 모니터링하세요:
webpack-bundle-analyzer나source-map-explorer로 번들 구성을 확인하세요. 사용하지 않는 라이브러리가 포함되어 있을 수 있습니다. - Third-party 스크립트를 관리하세요: 분석, 광고, 채팅 위젯 등 외부 스크립트가 성능 저하의 주범인 경우가 많습니다.
defer나async를 적용하세요. - Performance Budget을 설정하세요: “JS 번들 300KB 이하”, “LCP 2초 이하” 등 예산을 정하고, CI에서 자동으로 검사하세요.