LCP를 4.2s에서 2.1s로 줄인 방법
예전에는 성능 최적화를 조금 과하게 생각했던 것 같다.
메모리 사용량을 줄이고,
번들을 줄이고,
렌더링 횟수를 줄이는 것.
이런 것들이 성능 최적화의 전부라고 생각했다.
하지만 서비스를 운영하면서 생각이 조금 바뀌었다.
사용자는 Lighthouse 점수를 보지 않는다.
사용자가 느끼는 것은
이 서비스가 빠른가?
뿐이다.
그래서 이번 글에서는 실제로 LCP를 4.2초에서 2.1초까지 줄이면서 경험했던 과정들을 정리해보려고 한다.
성능 문제는 대부분 느낌으로 시작된다
처음에는 데이터보다 느낌이 먼저 있었다.
페이지가 뭔가 느렸다.
특히 첫 진입이 답답하게 느껴졌다.
개발 환경에서는 크게 느껴지지 않았지만 실제 사용자 환경에서는 분명히 차이가 있었다.
그래서 Lighthouse와 Chrome DevTools를 통해 측정을 시작했다.
npm install -g @lhci/cli
lhci autorun
결과는 생각보다 좋지 않았다.
LCP: 4.2s
Performance: 61
Core Web Vitals 기준으로도 좋지 않은 상태였다.
그제서야 느낌이 아니라 실제 문제라는 것을 확인할 수 있었다.
대부분의 원인은 이미지였다
성능 이슈를 분석하다 보면 결국 이미지인 경우가 많다.
우리도 마찬가지였다.
랜딩 페이지의 Hero 이미지가 PNG 원본 그대로 올라가 있었고 크기는 2MB가 넘었다.
<img src="/hero.png" alt="hero" />
당연히 LCP 요소가 늦게 렌더링됐다.
그래서 Next.js Image로 변경했다.
import Image from "next/image";
<Image src="/hero.webp" alt="hero" width={1200} height={630} priority />;
여기서 중요했던 것은 단순히 Image 컴포넌트가 아니었다.
priority
LCP 요소는 브라우저가 가장 먼저 가져와야 한다.
<Image priority />
이 한 줄이 생각보다 큰 차이를 만들었다.
WebP 변환
생각보다 PNG를 그대로 사용하는 경우가 많다.
이미지 품질 차이는 거의 없는데 용량은 절반 이하로 줄어든다.
결국 이미지 최적화만으로도 LCP가 상당히 개선됐다.
번들 분석을 해보니 생각보다 많은 것이 들어있었다
성능 최적화를 하면서 가장 자주 느끼는 것은
내가 로딩한다고 생각하지 않았던 것들이 이미 로딩되고 있다.
는 점이다.
bundle analyzer를 실행해봤다.
npm install @next/bundle-analyzer
ANALYZE=true npm run build
결과를 보고 조금 놀랐다.
대시보드에서만 사용하는 차트 라이브러리가 메인 번들에 포함되어 있었다.
import Chart from "chart-library";
사용자는 첫 페이지에서 차트를 보지도 않는데 이미 다운로드하고 있었던 것이다.
그래서 Dynamic Import를 적용했다.
const Chart = dynamic(() => import("chart-library"), {
ssr: false,
});
이후 초기 JS 번들 크기가 눈에 띄게 줄어들었다.
API는 생각보다 비싸다
프론트엔드 개발을 하다 보면 네트워크 요청을 너무 당연하게 생각하게 된다.
useEffect(() => {
fetchData();
}, []);
하지만 사용자가 페이지를 이동하고 다시 돌아올 때마다 같은 요청이 반복된다.
데이터가 자주 바뀌지 않는다면 낭비에 가깝다.
그래서 SWR을 사용하기 시작했다.
const { data } = useSWR("/api/dashboard", fetcher, {
dedupingInterval: 60000,
revalidateOnFocus: false,
});
이후에는 이미 받아온 데이터를 재사용하면서 화면이 훨씬 빠르게 느껴졌다.
특히 사용자의 체감 속도 개선이 컸다.
최적화보다 중요한 것은 측정이었다
돌아보면 가장 큰 배움은 최적화 기법 자체가 아니었다.
예전에는
이것도 최적화
저것도 최적화
를 하고 있었다.
지금은 먼저 측정한다.
측정
↓
원인 파악
↓
개선
↓
재측정
이 순서를 지키려고 한다.
성능 최적화는 생각보다 감에 의존하기 쉽다.
하지만 실제 병목은 예상과 다른 곳에 있는 경우가 많다.
최종 결과
최종적으로는 다음 정도의 개선을 얻을 수 있었다.
| 항목 | Before | After |
|---|---|---|
| LCP | 4.2s | 2.1s |
| TBT | 380ms | 90ms |
| Lighthouse | 61 | 98 |
숫자도 좋아졌지만 실제 사용자가 느끼는 속도도 확실히 달라졌다.
마무리
성능 최적화를 하면서 느낀 것은 결국 속도도 사용자 경험이라는 점이었다.
페이지가 1초 빨라진다고 새로운 기능이 생기는 것은 아니다.
하지만 사용자는 생각보다 속도에 민감하다.
재미있는 점은 사용자는
이 서비스의 LCP가 좋네요.
라고 말하지 않는다.
대신
뭔가 빠르다.
라고 말한다.
결국 성능 최적화는 점수를 높이기 위한 작업이 아니라 사용자의 기다림을 줄이는 작업에 더 가깝다고 생각한다.
그리고 대부분의 성능 문제는 복잡한 기술보다
이미지,
불필요한 번들,
중복 요청
같은 의외로 단순한 곳에서 시작되는 경우가 많았다.