웹 성능 최적화 실전 — Core Web Vitals 점수 올리기
3초에서 1.2초로 — 실제 개선 경험
2024년 초, 우리 웹사이트의 Google Page Speed Insight 점수는 참혹했다: 모바일 43점, 데스크톱 68점. 평균 로딩 시간 3초 이상에서 사용자 이탈률은 37%였다. 3개월간 성능 최적화에 집중한 결과: 로딩 1.2초, 이탈률 7%로 떨어짐. 매출에 직결되는 개선이었다.
Core Web Vitals — 세 가지 메트릭
LCP(Largest Contentful Paint)는 가장 큰 콘텐츠가 화면에 나타나는 시간이다. 목표는 2.5초 이내다. 우리는 4.2초에서 1.8초로 개선했다. FID(First Input Delay)는 사용자 입력에 반응하는 시간이고, 목표는 100ms 이내다. 우리는 450ms에서 45ms로 개선했다. CLS(Cumulative Layout Shift)는 예기치 않은 레이아웃 변경이고, 목표는 0.1 이내다. 우리는 0.25에서 0.02로 개선했다.
문제 1: 이미지가 너무 크다
대부분의 LCP 문제는 이미지 때문이다. 우리의 hero 이미지는 5.2MB(4000x3000px), 제품 이미지는 2-3MB씩 10개, 총 30MB가 넘었다. 해결책은 먼저 적절한 크기의 이미지를 제공하는 것이다. 모바일은 400px이면 충분하므로 800px 이미지만 만들어도 된다. Next.js의 Image 컴포넌트는 자동으로 최적화한다. LCP 이미지에는 priority 속성을 붙이고, 나머지는 loading=lazy로 설정한다. WebP 포맷을 사용하면 JPEG 대비 25% 크기가 감소한다. 결과: 30MB에서 2.8MB로 90% 감소.
문제 2: JavaScript가 너무 많다
JavaScript 실행이 메인 스레드를 블로킹했다. 번들 크기를 측정하니 React + libraries 400KB, third-party 600KB, custom 200KB, 총 1.2MB였다. Code splitting으로 필요한 코드만 로드하고, third-party 스크립트는 async로 로드하며, Web Worker로 무거운 작업을 오프로드한다. Tree shaking으로 불필요한 코드를 제거하고, 번들을 분석해서 큰 라이브러리를 찾는다. 결과: 1.2MB에서 350KB로 70% 감소.
문제 3: 폰트와 레이아웃
웹 폰트는 로드될 때까지 텍스트가 안 보이거나 깜빡일 수 있다. font-display: swap으로 시스템 폰트로 먼저 표시하고, 웹 폰트 로드 후 전환한다. 또는 preload로 폰트를 미리 로드한다. 레이아웃 시프트를 방지하려면 이미지에 미리 width/height를 설정하고, aspect-ratio로 공간을 예약한다. 광고는 최소 높이를 설정해서 공간을 미리 확보한다. 스켈레톤 로더로 데이터 로드 중에도 사용자 경험을 개선한다.
Lighthouse CI로 자동 모니터링
한 번 최적화하면 끝이 아니다. Lighthouse CI를 GitHub Actions에 연결해서 PR마다 성능 점수를 자동으로 확인한다. 성능이 기준 이하면 PR이 실패한다. 이렇게 하면 팀이 성능 회귀를 방지할 수 있다.
결과
3개월 후 LCP는 4.2초에서 1.8초, FID는 450ms에서 45ms, CLS는 0.25에서 0.02로 개선되었다. 전체 로드는 3.2초에서 1.2초, Page Speed 점수는 모바일 43점에서 88점으로 올랐다. 가장 중요한 건 사용자 이탈률이 37%에서 7%로 81% 감소했다는 것이다. 이탈률 감소는 직접 매출 증가로 이어진다.
결론
웹 성능은 기술의 문제가 아니라 신경 쓰는 정도의 문제다. 작은 최적화들이 모여서 큰 차이를 만든다.