성능 최적화 18편 - 번들 사이즈 분석과 축소로 로딩 속도 마무리하기
16편에서 코드 스플리팅으로 필요한 코드만 나눠 로드하는 방법을, 17편에서 이미지를 필요한 만큼만 가져오는 방법을 다뤘습니다. 이번 글에서는 프론트엔드 최적화의 마지막으로, 번들 자체의 크기를 줄이는 방법을 정리합니다. 아무리 코드를 잘 나눠도, 각 청크 안에 불필요한 코드가 쌓여 있다면 효과는 반감됩니다.
1. 번들 안에 무엇이 들어있는지부터 확인하기
최적화를 시작하기 전에, 지금 번들에 실제로 무엇이 얼마나 차지하고 있는지 먼저 확인해야 합니다. source-map-explorer나 webpack-bundle-analyzer 같은 도구를 사용하면 시각적으로 확인할 수 있습니다.
npm install --save-dev source-map-explorer
{
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'"
}
}
npm run build
npm run analyze
실행하면 브라우저에 트리맵(Treemap) 형태로 각 라이브러리가 차지하는 용량이 시각화되어 나타납니다. 이 시각화를 확인하다 보면 다음과 같은 패턴이 자주 발견됩니다.
main.[hash].js (2.1MB)
├─ moment.js 450KB <- 날짜 포맷팅에만 쓰는데 용량이 큼
├─ lodash (전체) 320KB <- 함수 몇 개만 쓰는데 전체를 포함
├─ chart.js 280KB <- 관리자 페이지에서만 쓰는데 메인 번들에 포함
└─ 실제 애플리케이션 코드 1050KB
2. 무거운 라이브러리를 가벼운 대안으로 교체하기
moment.js → day.js
moment.js는 기능이 풍부하지만 용량이 크고, 이미 유지보수 종료(deprecated)가 공지된 라이브러리입니다. 비슷한 API를 가진 day.js는 용량이 훨씬 작습니다.
moment.js : 약 290KB (gzip 기준 약 70KB)
day.js : 약 7KB (gzip 기준 약 2KB)
// 변경 전
import moment from 'moment';
const formatted = moment(date).format('YYYY-MM-DD');
// 변경 후
import dayjs from 'dayjs';
const formatted = dayjs(date).format('YYYY-MM-DD');
API가 거의 동일해서 마이그레이션 비용도 크지 않은 편입니다.
3. 라이브러리 전체 대신 필요한 함수만 가져오기
lodash처럼 여러 유틸리티 함수를 모아둔 라이브러리는, import 방식에 따라 번들에 포함되는 크기가 크게 달라집니다.
// 전체 라이브러리를 가져옴 (약 70KB)
import _ from 'lodash';
const result = _.debounce(fn, 300);
// 필요한 함수만 가져옴 (약 2KB)
import debounce from 'lodash/debounce';
const result = debounce(fn, 300);
첫 번째 방식은 debounce 함수 하나만 쓰더라도 lodash 전체가 번들에 포함됩니다. 두 번째처럼 개별 함수 경로로 import하면, 실제로 사용하는 함수만 번들에 들어갑니다.
4. Tree Shaking이 제대로 동작하는지 확인하기
Tree Shaking은 실제로 사용되지 않는 코드(export했지만 import되지 않은 함수 등)를 빌드 시점에 자동으로 제거해주는 기능입니다. 하지만 다음과 같은 경우 Tree Shaking이 제대로 동작하지 않을 수 있습니다.
// utils.js - 개별 함수로 export
export function formatPrice(price) { /* ... */ }
export function formatDate(date) { /* ... */ }
// 잘못된 예 - default export로 객체 전체를 내보내면 Tree Shaking이 어려움
export default {
formatPrice,
formatDate,
};
객체 하나로 묶어서 export하면, 빌드 도구 입장에서는 어떤 속성이 실제로 쓰이는지 정적으로 분석하기 어려워 전체를 포함시켜버립니다. 개별 함수를 named export로 내보내는 방식이 Tree Shaking에 더 유리합니다.
5. 사용하지 않는 라이브러리 찾아내기
프로젝트가 오래되면, 이제는 쓰이지 않는데 여전히 설치만 되어 번들에 포함되는 라이브러리가 남아있는 경우가 많습니다.
npx depcheck
Unused dependencies
* jquery
* moment
* react-datepicker
depcheck 같은 도구로 실제 코드에서 import되지 않는 의존성을 찾아내고, 확인 후 제거하면 그만큼 번들 크기가 줄어듭니다.
6. gzip / brotli 압축 확인하기
번들 파일 자체의 크기도 중요하지만, 실제로 네트워크를 통해 전송되는 크기는 서버의 압축 설정에 따라 달라집니다.
원본 크기 : 450KB
gzip 압축 후 : 145KB
brotli 압축 후 : 128KB
Nginx나 CDN 설정에서 gzip 또는 그보다 압축률이 높은 brotli가 활성화되어 있는지 확인하는 것만으로도, 코드 수정 없이 전송 용량을 크게 줄일 수 있습니다.
gzip on;
gzip_types text/css application/javascript;
brotli on;
brotli_types text/css application/javascript;
7. 적용 전후 비교
[최적화 전]
main.bundle.js (gzip 기준) : 620KB
초기 로딩 시간 : 2.4초
[최적화 후]
day.js 교체, lodash 개별 import, 미사용 라이브러리 제거, brotli 압축 적용
main.bundle.js (gzip 기준) : 210KB
초기 로딩 시간 : 0.9초
8. 정리
- 번들 사이즈를 줄이기 전에, 분석 도구로 실제 무엇이 용량을 차지하는지부터 확인한다
- 용량이 큰 라이브러리는 더 가벼운 대안으로 교체하거나(moment.js → day.js), 필요한 함수만 개별 import 한다
- named export 위주로 코드를 작성해야 Tree Shaking이 효과적으로 동작한다
depcheck등으로 더 이상 쓰이지 않는 의존성을 찾아 정리한다- 코드 수정 없이도 서버의 압축 설정(gzip, brotli)만으로 전송 용량을 줄일 수 있다
16편의 코드 스플리팅, 17편의 이미지 최적화, 그리고 이번 편의 번들 축소까지, 프론트엔드 로딩 최적화는 결국 "지금 이 화면에 진짜 필요한 것이 무엇인가"를 계속 되묻는 과정이라는 점에서 백엔드의 쿼리 최적화와 같은 결을 가지고 있습니다.
다음 글부터는 다시 백엔드로 돌아가, 지금까지 다루지 않았던 새로운 최적화 주제를 이어가 보겠습니다.
'성능 최적화' 카테고리의 다른 글
| 성능 최적화 STEP 20 - 네트워크 비용 줄이기 (0) | 2026.07.15 |
|---|---|
| 성능 최적화 STEP 19 - 트랜잭션 격리 레벨 (0) | 2026.07.15 |
| 성능 최적화 STEP 17 - 이미지 최적화 (0) | 2026.07.14 |
| 성능 최적화 STEP 16 - 초기 로딩 속도 개선하기 (1) | 2026.07.13 |
| 성능 최적화 STEP 15 - 불필요한 리렌더링 줄이기 (0) | 2026.07.13 |