Contents
- LightHouse란 무엇인가?
- LightHouse 점수 분석해보기
- 코드 스플리팅 적용하기
- 마무리
1. LightHouse란 무엇인가?
웹 애플리케이션의 성능은 사용자 경험과 검색 엔진 최적화(SEO)에 큰 영향을 미친다.
이러한 중요성 때문에 웹 성능 분석 및 개선 경험이 개발자 채용 요구사항에서도 자주 등장한다.
이번에는 LightHouse를 이용해서 블로그의 성능 개선 경험을 정리해보겠다.
LightHouse는 웹 품질을 개선하는데 사용하기 위해 Google에서 개발한 오픈소스 도구다.
성능, 접근성, 검색 엔진 최적화 등 여러 웹 품질 지표를 분석하고 개선할 수 있는 인사이트를 제공한다.
Chrome의 개발자 도구로 쉽게 접근할 수 있고, CI/CD 파이프라인에 자동화하여 지속적으로 모니터링도 가능하다.
지표
LightHouse는 0에서 100점 사이의 점수로 웹의 다양한 측면을 평가한다.
각 카테고리는 다음과 같다.
- Performance (성능)
- Accessibility (접근성)
- Best Practices (모범 사례)
- SEO (검색 엔진 최적화)
- PWA (Progressive Web App)
이번 글에서는 주로 성능에 초점을 맞추어 LightHouse를 다뤄보겠다.
Performance(성능)
성능 점수는 6가지의 메트릭으로 페이지가 로드되는 속도를 측정한다.
- First Contentful Paint (FCP) : 페이지가 로드되기 시작한 시점부터 초기 DOM 콘텐츠가 화면에 렌더링될 때까지의 시간
- Largest Contentful Paint (LCP) : 가장 큰 콘텐츠 요소가 화면에 렌더링되는 시간
- Total Blocking Time (TBT) : FCP와 TTI 사이의 시간 중 작업이 오래 실행되어 주 스레드를 차단한 총 시간
- Cumulative Layout Shift (CLS): 페이지 로드 중 발생하는 예기치 않은 레이아웃 이동의 정도
- Speed Index (SI) : 콘텐츠가 시각적으로 표시되는 진행 속도
- Time to Interactive (TTI) : 페이지가 완전히 상호작용 가능한 상태가 되는 시간
각 메트릭의 개선 방법
- FCP: 서버 응답 시간 개선, 렌더링 차단 리소스 제거
- LCP: 이미지 최적화, 필요한 리소스 미리 로드
- TBT: 코드 스플리팅, 웹 워커 사용
- CLS: 이미지와 광고에 크기 속성 지정, 동적 콘텐츠 삽입 시 주의
- SI: 메인 스레드 작업 최소화, JavaScript 실행 시간 단축, Fallback 폰트 적용
- TTI: JavaScript 실행 시간 단축, 불필요한 JavaScript 제거
2. LightHouse 점수 분석하기

개선 전, 웰컴 페이지와 1번 게시글 페이지의 LightHouse 점수는 위의 사진과 같다.

두 분석에서 공통적으로 도출되는 개선사항이 아래 두가지이다.
- Largest Contentful Paint Element - Render Delay (가장 큰 콘텐츠 요소 렌더링 지연)
- Reduce unused JavaScript (사용하지 않는 자바스크립트 코드 제거)
위의 두 가지 항목을 개선하기 위해, 코드 스플리팅을 통해 번들링 최적화를 진행하기로 결정했다.
3. 코드 스플리팅 적용하기
번들링과 코드 스플리팅
앱이 커지면 당연히 번들도 커진다.
특히 클라이언트 사이드 렌더링을 할 때 초기 js 파일이 크다면 사용자는 그만큼 오랜 시간을 기다려야한다.
이를 해결하기 위한 여러가지 방법 중(이미지, 폰트와 같은 에셋 최적화), 대표적으로 번들링 사이즈를 최적화하는 방법이 있다.
그 중에서도 코드 스플리팅은 효과적인 방법 중 하나이다.

코드 스플리팅 이전의 번들 사이즈는 사진과 같다.
사진을 보면 알겠지만, 스플리팅 전에는 하나의 js 파일로 모든 파일이 번들링된다.
용량도 무려 1,300kb 가량 된다.
그리고 Vite도 청크(코드의 작은 조각)의 압축 후에도 500kb보다 크다고 경고를 보내주고 있다.
코드 스플리팅은 어떻게 하는가?
코드 스플리팅을 구현하기 위해 React lazy를 사용한다.
lazy는 컴포넌트를 처음 렌더링하는 순간까지 렌더링을 연기할 수 있게 해준다.
나는 가장 대표적인 방법인, 라우트 기반 스플리팅(Route based Code Splitting) 을 사용했다.
라우트 기반 스플리팅은 페이지(라우트) 별로 청크를 분리하여, 페이지 별로 로딩 시키는 방식이다.
따라서 Router가 있는 컴포넌트를 아래와 같이 수정했다.
import { lazy, Suspense } from 'react'; import { Route, Routes } from 'react-router-dom'; // 레이지 로딩을 사용하여 컴포넌트 임포트 const Home = lazy(() => import('../../pages/home/Home')); const PostList = lazy(() => import('../../pages/postlist/PostList')); const About = lazy(() => import('../../pages/about/About')); const PostDetail = lazy(() => import('../../pages/postdetail/PostDetail')); // 로딩 중 표시할 컴포넌트 export default function Main() { return ( <div> <Suspense fallback={null}> <Routes> <Route path="/" element={<Home />} /> <Route path="/post" element={<PostList />} /> <Route path="/post/:postId" element={<PostDetail />} /> <Route path="/about" element={<About />} /> </Routes> </Suspense> </div> ); }

그러나 라우트 기반 스플리팅을 했음에도 MarkdownRenderer 컴포넌트의 번들링 사이즈가 너무 크다.
MarkdownRenderer 컴포넌트 최적화
원인을 찾고자 컴포넌트를 분석해보면, MarkdownRenderer에서 import하는 SyntaxHighlighter 라이브러리의 사이즈가 너무 크다는 것을 알 수 있다.
SyntaxHighlighter를 처음 사용할 때는 빌드에 대한 부분을 간과했었지만, react-syntax-highlighter github를 보면 가벼운 빌드를 위한 방법을 제공해준다.
import { PrismLight as SyntaxHighlighter } from 'react-syntax-highlighter'; import jsx from 'react-syntax-highlighter/dist/esm/languages/prism/jsx'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; SyntaxHighlighter.registerLanguage('jsx', jsx);
위의 코드와 같은 방식으로 Prism이 아닌 PrismLight로 import하고 언어를 명시해주면 훨씬 가벼운 번들 사이즈로 SyntaxHighlighter를 사용할 수 있다.
최종적으로 번들링 결과는 다음과 같이 개선되었다.

마무리
코드 스플리팅과 라이브러리 최적화를 통해 번들 사이즈를 효과적으로 줄일 수 있었다.
특히 라우트 기반 코드 스플리팅과 react-syntax-highlighter 라이브러리 빌드 경량화 사용으로 초기 로딩에 사용되는 JavaScript 코드의 사이즈를 크게 감소시켰다.


그러나 예상과 달리, 이러한 번들링 최적화에도 불구하고 LightHouse 점수는 크게 개선되지 않았다.
이는 웹 성능이 단순히 JavaScript 파일 크기만의 문제가 아니라는 점을 시사하며, 이러한 결과를 통해 우리는 성능 최적화가 단순히 하나의 요소만을 개선하는 것으로는 충분하지 않다는 것을 깨달았다.
따라서 다음 단계로 웹 애플리케이션의 전반적인 렌더링 로직, 특히 마크다운을 렌더링하는 부분을 재검토하기로 결정했다.