코드 스플리팅, 무작정 나누기보다 전략적으로 접근하기

keemsebeen·2025년 9월 27일
3
post-thumbnail

안녕하세요. 세라입니다.
이번 글에서는 초기 로딩 성능을 개선하기 위해 코드 스플리팅을 적용하면서 마주한 역설적인 문제,
그리고 그것을 데이터 기반으로 유연하게 해결한 과정을 공유드리려 합니다.

초기 상황 - 현재 번들 크기와 개선이 필요한 이유

먼저, 기존 상태의 네트워크 탭을 살펴보도록 하겠습니다.

현재 DOM 구축은 약 130ms로 빠르게 완료되었지만 전체 리소스 로딩에는 480ms가 소요되었습니다.
즉, 사용자는 빠르게 페이지의 뼈대를 보지만, 실제 상호작용 가능한 시점은 리소스 로딩 속도에 의해 지연되고 있음을 확인할 수 있었습니다.

따라서, 저는 "그렇다면 페이지 별로 필요한 JS만 로드하면 더 빨라지지 않을까?"라는 가설을 세우고 모든 페이지에 코드 스플리팅을 전면 적용해 보기로 했습니다.

첫번째 시도: 무작정 스플리팅

각 페이지 진입 시마다 필요한 js파일만 내려받도록 코드 스플리팅을 전면 적용했습니다. 즉, 각 페이지 컴포넌트를 lazy()로 감싸고, 진입할 때마다 해당 청크를 동적으로 로드하는 구조였습니다.

적용 후 네트워크 탭을 확인해보니, 전송량 자체는 크게 달라지지 않았지만 리소스 로드 시간이 약간 단축되는 효과가 있었습니다.
하지만 단순히 시간만으로는 성능 개선을 단정하기 어려웠기에, 보다 구체적인 비교를 위해 번들 구조를 시각적으로 분석할 수 있는 BundleAnalyzerPlugin을 활용했습니다.

구분 Stat Parsed Gzipped
기존 기존 Stat 이미지 2.53 MB
기존 Parsed 이미지 876 KB
기존 Gzipped 이미지 227 KB
무작정 스플리팅 후 스플리팅 Stat 이미지 2.6 MB
스플리팅 Parsed 이미지 977 KB
스플리팅 Gzipped 이미지 278 KB

보시는 것처럼, 코드 스플리팅을 적용한 이후 오히려 번들 크기가 증가했습니다.
“분리했는데 왜 커졌을까?”라는 의문과 함께, 초기 로딩 성능 개선을 목표로 한 작업이 과연 실질적인 개선이었는지 되돌아보게 되었습니다.

처음엔 단순히 “조금 더 나눴을 뿐인데 왜 커졌을까?” 정도로 생각했지만,
점점 혹시 내가 잘못된 방향으로 최적화를 한 건 아닐까?라는 불안감이 들었습니다. 따라서 단순히 결과만 보는 게 아니라, 무엇이 이 변화를 만들었는지 구조적으로 분석해보기로 했습니다.

문제 원인 분석

구체적으로 원인을 살펴보니, 다음 네 가지 요인이 있었습니다.

1. 중복 포함 문제

코드 스플리팅 과정에서 공통 모듈이 제대로 vendor 청크나 common 청크로 묶이지 않으면, 각 lazy 로딩된 청크마다 동일한 코드(ex. react)가 중복 포함될 수 있습니다. 따라서 webpack의 splitChunks이 최적화되지 않은 경우는 번들 크기가 증가할 수 있습니다.

2. 런타임/로더 오버헤드

lazy 로딩을 하면 각 청크를 불러오기 위한 webpack runtime 코드나 import 로더 코드가 추가됩니다. 따라서 모듈을 잘게 나눌수록 이런 부가 코드가 누적되면서 번들 크기가 늘어납니다.

3. 코드 분할 단위의 비효율

작은 컴포넌트는, 실제 js 코드 크기보다 청크 관리용 메타데이터 + 로더 코드가 더 클 수 있습니다. 따라서 분리해서 얻는 이득 < 분리로 인해 추가되는 부하가 더 큰 상황이었습니다.

4. 중복 리소스 요청 (ex. 폰트, 스타일, 이미지)

특정 페이지 전용 리소스를 lazy 로딩 청크에 포함시키면, 원래는 한 번만 받던 리소스가 여러 청크에 포함될 수 있습니다.

전략적 접근

이 경험을 통해 깨달은 점은 명확했습니다.

코드 스플리팅은 무작정 적용한다고 성능이 개선되는 것이 아니다.
오히려 불필요한 청크 분리로 인해 번들 크기 증가나 네트워크 요청 과다가 발생할 수 있다.

따라서 데이터 기반의 전략적 접근이 필요하다고 판단했습니다.
이를 위해 GA(Google Analytics) 데이터를 활용해, 실제 사용자 이동 경로를 분석했습니다.

분석 결과, 사용자들이 자주 방문하는 주요 페이지는 메인, 조직 조회,이벤트 전체 조회, 이벤트 상세 조회였습니다. 이에 따라 해당 페이지들은 초기 번들에 포함시켜, 반복 방문 시 캐시를 재활용하도록 구성했습니다.

또한 GA 데이터를 기반으로, 페이지별 접근 빈도에 따라 아래와 같은 전략을 수립했습니다.

  • 자주 방문하는 페이지 → 초기 번들에 포함하여 캐싱 효과 극대화
  • 외부 진입/드물게 방문하는 페이지 → Lazy Loading 적용, 필요할 때만 네트워크 요청 발생

이렇게 함으로써 불필요한 초기 번들 크기 증가를 방지하고, 각 페이지의 성격과 사용 패턴에 따라 맞춤형 스플리팅 전략을 수립할 수 있었습니다.

전략의 핵심 이유

초기 로딩 경험 개선

자주 방문하는 페이지를 lazy 로딩하면, 사용자는 진입할 때마다 js 청크를 새로 내려받게 됩니다. → 첫 화면 렌더링이 오히려 늦어질 수 있습니다.
반면, 초기 번들에 포함하면 이미 로드된 코드로 즉시 렌더링이 가능해 체감 속도가 빨라집니다.

네트워크 비용 절감 (캐싱 효과)

재방문 가능성이 높은 페이지를 별도 청크로 분리하면, 페이지 진입 시마다 네트워크 요청이 발생합니다.
초기 번들에 포함시켜 한 번만 내려받게 하면, 이후에는 브라우저 캐시를 활용할 수 있어 네트워크 트래픽을 줄일 수 있습니다.

사용자 여정 기반 최적화

실제 사용자 중 70~80%가 메인 → 서브 페이지 순으로 이동한다면, 메인 페이지를 분리하는 것은 실익이 거의 없습니다.

Trade-off 인식

포함 시 장점: 초기 로딩 빠름, 캐시 활용, 자주 쓰는 페이지 체감 성능 ↑
포함 시 단점: 초기 번들 크기 증가 → 느린 네트워크(모바일, 3G)에서는 다운로드 부담

최종 결과 비교

전략적 접근을 통해 사용자 진입 패턴을 고려한 선택적 코드 스플리팅을 적용했습니다. 핵심 페이지는 초기 번들에 유지하고, 관리자 페이지나 통계 대시보드처럼 소수만 사용하는 기능에만 지연 로딩을 적용했습니다.

아래는 각 시나리오별 번들 크기 비교입니다.

구분 Stat Parsed Gzipped
기존 기존 Stat 이미지 2.53 MB
기존 Parsed 이미지 876 KB
기존 Gzipped 이미지 227 KB
무작정 스플리팅 후 스플리팅 Stat 이미지 2.6 MB
스플리팅 Parsed 이미지 977 KB
스플리팅 Gzipped 이미지 278 KB
전략적 코드 스플리팅 후 전략적 코드 스플리팅 Stat 이미지 2.4 MB
스플리팅 Parsed 이미지 945 KB
스플리팅 Gzipped 이미지 258 KB

배운 점

최근 본 영상에서 “무언가를 개선하면 다른 한쪽에서는 희생을 했다는 의미이다.”라는 말을 들었습니다. 그 전까지는 이 말이 쉽게 와닿지 않았는데요. 최적화를 진행한 것인데 어디서 희생이 있었다는 거지?, 코드 보는 시간이 길어지니 개발자의 시간을 희생했다는 것일까..? 하는 생각도 들었습니다.

하지만 이번 코드 스플리팅 경험을 통해, 그 말의 의미를 비로소 이해하게 되었습니다.

처음에는 단순히 “코드 스플리팅을 하면 각 페이지에 필요한 js만 로드하니 무조건 이득이겠지”라고 생각했습니다. 또 “코드 스플리팅은 단지 모듈을 쪼개는 과정이니 번들 크기에는 큰 영향이 없을 것”이라 예상했는데요.

그러나 실제로 적용해보니 번들 크기가 오히려 증가했고, 이 순간 성능 최적화가 항상 플러스 요인은 아니라는 사실에 대해 깨달았던 것 같아요.

또, 최적화를 왜 해야 하는지, 어디에 하는 것이 좋은지에 대해 많은 생각을 했던 것 같습니다. 기술적 베스트 프랙티스를 무조건 적용하는 것보다, 해당 서비스의 맥락에서 어떤 최적화가 실제로 도움이 되는지 판단하는 것이 중요했습니다.

결국 최적화란 단순히 “무조건 더 빠르게 만드는 것”이 아니라, 사용자가 실제로 체감할 수 있는 부분에 집중하여 합리적인 개선을 이루는 과정이라는 것을 배웠습니다. 숫자상의 개선보다는 실제 사용 시나리오에서의 경험 향상이 더 중요하며, 이를 위해서는 데이터와 맥락을 종합적으로 고려한 전략적 접근이 필요합니다.

저는 이번 경험을 통해 성능 최적화는 단순한 기술적 과제가 아니라, 사용자 경험을 중심에 둔 선택의 과정임을 다시금 느꼈습니다. 앞으로도 지표에만 매몰되지 않고, 실제 사용자가 체감하는 가치를 기준으로 판단하는 개발자가 되어야 겠다고 다짐하게 되었습니다!

끗!

profile
프론트엔드 공부 중인 김세빈입니다. 👩🏻‍💻

0개의 댓글