사례 연구: React 번들 크기를 68% 줄인 방법

발행: (2025년 12월 13일 오후 05:31 GMT+9)
5 min read
원문: Dev.to

Source: Dev.to

Introduction

저는 B2B 애플리케이션을 작업하면서 흔히 있는 오해를 마주했습니다. “사용자는 사무실 PC에서 빠른 인터넷을 사용하니 구글 코어 웹 바이탈스와 번들 크기가 커도 괜찮다”는 생각이었습니다. 실제로는 주요 사용자가 모바일 인터넷을 사용하는 스마트폰으로 애플리케이션에 접속하고 있었습니다. 이를 알게 된 뒤 최적화가 급선무가 되었습니다.

시작점: 프로덕션 번들 크기는 1,542 KB(gzip 압축)였습니다.
목표: 500 KB (업계 모범 사례에 따름)

Step 1: Compression – Already Optimized

첫 번째 당연한 단계는 Brotli 압축을 적용하는 것이었습니다. Brotli는 일반적으로 gzip보다 15–25 % 정도 더 절감해 줍니다. 하지만 인프라에 이미 Brotli가 적용돼 있었기 때문에 여기서는 추가 이득을 얻을 수 없었습니다.

Step 2: Route‑Based Code Splitting

React Router는 기본적으로 lazy loading을 지원합니다. 모든 라우트에 동적 import를 적용했습니다:

// Before
import Dashboard from './Dashboard';

// After
const Dashboard = React.lazy(() => import('./Dashboard'));

결과: 번들이 37 % 감소해 971 KB가 되었습니다.

Step 3: Dependency Analysis & Vendor Splitting

Webpack Bundle Analyzer를 사용해 번들을 장악하고 있던 무거운 서드파티 라이브러리를 발견했습니다:

  • 날짜 선택/캘린더 라이브러리
  • Feature‑flag 관리
  • Analytics SDK
  • PDF 편집기
  • WYSIWYG 편집기
  • 파일 업로드 위젯

이들 라이브러리는 여러 모듈에서 직접 import되고 있었습니다. 동적 import를 이용한 래퍼 모듈을 만들었습니다:

// Before – Direct imports everywhere
import { DatePicker } from 'heavy-date-library';

// After – Wrapper with lazy loading
export const loadDatePicker = () =>
  import('heavy-date-library').then(module => module.DatePicker);

결과: 번들이 원본의 **57 %**인 879 KB로 감소했습니다.

Step 4: Module Decoupling & Dependency Chains

번들 분석 결과, 의존성 체인이 매우 촘촘히 엮여 있음을 확인했습니다. 예시:

// ❌ Tightly coupled
// Module A imported Module B, which imported Module C,
// which imported a heavy utility library
// All loaded together even if only one piece was needed

// ✅ Solution: Extract shared code to independent modules
// Created small, focused modules with only necessary shared code
// Broke circular dependencies

이러한 체인을 식별하고 분리해, 필요한 공유 코드만 포함한 작고 집중된 모듈을 만들었습니다.

결과: 63 % 감소한 571 KB가 되었습니다.

Step 5: Localization Optimization

애플리케이션은 여러 언어를 지원했지만, 사용자 선호와 무관하게 모든 번역 파일을 동기식으로 로드하고 있었습니다.

Before: 모든 언어 파일이 하나의 번들에 포함되어 있었음(≈ 120 KB JSON).
After: 사용자가 선택한 언어만 초기 로드하도록 변경:

// Dynamic locale loading
const loadLocale = (locale) =>
  import(`./locales/${locale}.json`);

최종 결과: 493 KB68 % 감소로 500 KB 목표를 초과 달성했습니다!

Key Takeaways

  • 가정은 위험하다: 데이터 없이 사용자 상황을 추정하지 마세요.
  • 분석 후 최적화: Webpack Bundle Analyzer가 큰 도움이 되었습니다.
  • 서드파티 라이브러리는 비용이 크다: 무거운 의존성을 전략적으로 관리하세요.
  • 의존성 체인이 중요하다: 촘촘한 결합은 번들 부피를 늘립니다.
  • 현지화도 무거울 수 있다: 언어 파일을 동적으로 로드하세요.
Back to Blog

관련 글

더 보기 »

번들을 과체중이 되지 않게 하세요

솔직히 말하자면, 우리 모두 번들 크기를 신경 씁니다. 수년간 bundlesize가 대표적인 도구였지만, 이제는 오래되고 유지보수가 되지 않습니다. 보안 검사가 플래그를 달기 시작했습니다.