Vite 빌드 출력 최적화: 트리 쉐이킹 실전 가이드

발행: (2026년 5월 23일 AM 02:53 GMT+9)
8 분 소요
원문: Dev.to

Source: Dev.to

번들 최적화는 남의 일이라고 생각하던 시절이 있었습니다. import * as utils from './utils' 같은 편리한 네임스페이스 임포트를 사용하고, npm run build를 실행한 뒤 나오는 결과물을 그대로 배포했죠. 번들의 크기는 점점 커졌습니다: 200KB, 300KB, 450KB… 하지만 괜찮다고 스스로를 달랬습니다. 브라우저는 점점 좋아지고, 인터넷 속도는 빨라지고, 디바이스는 더 강력해지고 있잖아요.

그런데 450KB짜리 유틸리티 라이브러리를 3G 환경에서 테스트해 보니, 다운로드에 4초가 걸렸습니다. Lighthouse는 부끄러운 성능 점수를 주었고, 저는 사용되지 않는 코드가 60%나 포함돼 있다는 사실을 깨달았습니다.

이 글에서는 Vite에서 트리 쉐이킹을 적용하면서 발견한 점, 제가 저지른 실수들, 그리고 이를 어떻게 고쳤는지에 대해 다룹니다.

트리 쉐이킹이란?

트리 쉐이킹은 ES 모듈에서 사용되지 않는 코드를 제거하는 dead code elimination입니다. importexport를 사용하면 정적인 의존성 그래프가 만들어지고, Vite(내부적으로 Rolldown 사용)는 이 그래프를 추적해 사용되지 않는 export를 삭제합니다. 즉, 트리 쉐이킹은 번들러가 코드가 사용되지 않음을 증명할 수 있을 때만 작동합니다. 애매한 신호를 주면 번들러는 모든 것을 안전하게 보존합니다.

실제 예시: dashboard.utils.ts

dashboard.utils.ts라는 유틸리티 파일이 있다고 가정해 봅시다. 여기서는 다음과 같은 8개의 독립 함수가 export됩니다.

  • hasActiveFilters
  • mapApplicationToTableData
  • resetPagination
  • updateFilterState
  • updatePagination
  • mapJobStats
  • normalizeFilter
  • buildPagination

그 중 resetPagination 하나만 필요했지만, 저는 다음과 같이 전체 모듈을 임포트했습니다.

import * as utils from './dashboard.utils';

Vite는 정적 분석을 통해 여기서 실제로 사용된 것이 resetPagination뿐이라는 것을 알아내고 나머지는 제거할 수 있습니다. 하지만 이는 번들러가 여러분을 대신해 해주는 것이지, 여러분이 의도적으로 코드를 작성한 것이 아닙니다. 전체 모듈을 임포트하고 정리 작업을 빌드 도구에 맡긴 것이죠. 상황에 따라 잘 동작하지만, 언제든 깨질 수 있습니다.

문제가 되는 패턴

console.log(utils);

또는

const obj = { ...utils };

처럼 네임스페이스 객체를 console.log에 넘기거나 다른 구조에 스프레드하면, 빌드 시점에 어떤 프로퍼티가 실제로 사용될지 알 수 없게 됩니다. 따라서 번들러는 안전을 위해 8개의 export 전부를 보존합니다: hasActiveFilters, mapJobStats, buildPagination 등 모든 함수가 포함됩니다.

이것은 함정입니다. 첫 번째 버전은 전혀 위험해 보이지 않으며, 린터를 통과하고 빌드도 성공합니다. 그런데 몇 주 뒤에 누군가 디버그 로그를 추가하거나 네임스페이스를 유틸리티에 넘기면, 눈치채지 못한 채 번들에 불필요한 무게가 실리게 됩니다.

  • 임포트된 대시보드 유틸 함수 8개
  • 실제로 사용된 함수 1개
  • 나머지 7개가 매 페이지 로드 시마다 함께 번들에 포함

이처럼 명확한 신호를 번들러에 주면 resetPagination만 남기고 mapJobStats, buildPagination, normalizeFilter 등은 최종 출력물에서 안전하게 제거됩니다.

네임스페이스 임포트 → 정확한 명명된 임포트로 전환

네임스페이스 임포트와 과다 임포트를 버리고, 필요한 함수만 명시적으로 임포트하는 것이 가장 큰 개선점이었습니다.

/* @__PURE__ */ 주석

빌드 결과물에 /* @__PURE__ */ 주석이 보이기 시작했습니다. 이 주석은 미니파이어에게 해당 함수 호출에 부수 효과가 없다고 알려주어, 반환값이 사용되지 않으면 전체 호출을 안전하게 제거하도록 합니다.

위 스니펫에서 resetPaginationbuildPagination은 입력을 받아 값을 계산하고 반환할 뿐이며, HTTP 호출, 상태 변이, DOM 접근 등 부수 효과가 전혀 없습니다. 이런 순수 함수는 Vite가 “pure”라고 표시할 때 정확히 찾는 대상입니다.

과도한 eager import는 피하라

프로젝트가 성장하면서 helpers/ 디렉터리 안에 date.helper.ts, dashboard.utils.ts 등 여러 유틸 파일이 생깁니다. 이때 모든 파일을 한 번에 임포트하고 싶어지는 유혹이 있습니다.

import * as dateHelper from './helpers/date.helper';
import * as dashboardUtils from './helpers/dashboard.utils';
import * as otherHelper from './helpers/other.helper';

그 결과 모든 페이지 컴포넌트와 그 의존성, 템플릿까지 첫 요청 시에 번들에 포함되어 로드됩니다. 사용자가 실제로 방문하는 페이지와는 무관합니다.

대신 페이지마다 lazy‑load를 적용해 각각 별도의 청크로 분리하면, 예를 들어 대시보드 페이지만 방문하는 사용자는 프로필 관리나 잡스 페이지 청크를 다운로드하지 않게 됩니다. 라우터가 loadPage('dashboard')를 호출할 때만 해당 청크가 로드되고, 나머지는 필요할 때

0 조회
Back to Blog

관련 글

더 보기 »

내 스킬

프로젝트를 위한 AI 지시문을 만들고, 설치하고, 관리하세요 — 코딩이 필요 없습니다. CREATE 이름을 정하고, 카테고리를 선택하고, 원하는 것을 설명하세요 — 마법사가 자동으로 구성합니다.