본문으로 건너뛰기
Previous
Next
Vite 5 고급 가이드 — Rollup·플러그인·HMR·프로덕션 최적화

Vite 5 고급 가이드 — Rollup·플러그인·HMR·프로덕션 최적화

Vite 5 고급 가이드 — Rollup·플러그인·HMR·프로덕션 최적화

이 글의 핵심

Vite 5에서 프로덕션 번들러로 쓰이는 Rollup 4와의 연동 방식, 환경별 설정, 커스텀 플러그인 작성, HMR 병목 제거, 프로덕션 청크 전략, React·Vue·Svelte 공식 플러그인과의 조합을 한 번에 정리합니다.

이 글의 핵심

Vite 5는 개발 서버에서는 네이티브 ESM과 esbuild 기반의 사전 번들로 빠른 시작을 제공하고, 프로덕션 빌드에서는 Rollup 4를 통해 트리 쉐이킹·코드 스플리팅·에셋 파이프라인을 완성합니다. 본 글은 입문용 “설치와 기본 설정”을 넘어, 환경별 구성, 커스텀 플러그인, HMR 튜닝, 번들 최적화, React·Vue·Svelte 통합을 실무 관점에서 연결합니다.

선행 지식: vite.config.ts, ESM, import.meta, Node 18+ 환경에 익숙하다고 가정합니다. Vite 기초는 저장소 내 vite-5-complete-guide 등 입문 글을 먼저 보시면 흐름이 매끄럽습니다.


1. 아키텍처: 개발 서버와 프로덕션 번들

1.1 esbuild와 Rollup의 역할 분담

개발 모드에서 Vite는 의존성을 esbuild로 사전 번들(pre-bundle) 하고, 애플리케이션 소스는 변환만 거쳐 브라우저 네이티브 ESM으로 제공합니다. 반면 vite buildRollup을 통해 청크 그래프를 만들고, 압축·에셋 처리·CSS 분리 등을 수행합니다. 따라서 성능 이슈를 “개발”과 “빌드”로 나누어 진단하는 것이 첫 단계입니다.

1.2 Vite 4 대비: Rollup 3에서 Rollup 4로

Vite 5는 내부적으로 Rollup 4 계열을 사용합니다. 기존 Vite 4 + Rollup 3 시절의 커스텀 플러그인·설정을 이식할 때는 다음을 특히 점검합니다.

  • Import attributes 등 문법·플러그인 API 변화
  • this.resolve / this.parse 기본 동작 차이로 인한 해석 순서 변화
  • 순수 ESM 환경에서의 패키지 exports 필드 해석

공식 마이그레이션 문서의 브레이킹 체인지 목록과 함께, Lockfile과 peer dependency를 재설치하여 재현 가능한 빌드를 만드는 것이 안전합니다.


2. 환경별 설정: 모드, 디렉터리, loadEnv

2.1 defineConfig와 조건부 분기

운영·스테이징·로컬에서 동일한 저장소를 쓰되 엔드포인트·플래그만 바꾸려면, 설정 파일에서 modecommand를 기준으로 분기하는 패턴이 흔합니다.

// vite.config.ts
import { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig(({ mode, command }) => {
  const env = loadEnv(mode, process.cwd(), '');
  const isProd = mode === 'production';

  return {
    plugins: [react()],
    define: {
      __APP_VERSION__: JSON.stringify(env.VITE_APP_VERSION ?? '0.0.0'),
    },
    server: {
      port: Number(env.VITE_DEV_PORT) || 5173,
    },
    build: {
      sourcemap: !isProd,
    },
  };
});

loadEnv.env, .env.local, .env.[mode] 등을 모드에 맞게 병합합니다. 클라이언트에 노출할 변수만 VITE_ 접두사를 붙이는 관례를 지키면, 실수로 서버 전용 비밀을 번들에 싣는 사고를 줄일 수 있습니다.

2.2 NODE_ENVimport.meta.env의 관계

Vite는 import.meta.env.MODE, import.meta.env.PROD, import.meta.env.DEV 등을 주입합니다. 라이브러리 코드에서 process.env.NODE_ENV를 직접 참조하는 경우, define 치환 또는 호환 플러그인이 필요할 수 있으므로, 배포 파이프라인에서 프로덕션 빌드 한 번으로 재현되는지 꼭 확인합니다.


3. Rollup 옵션과 Vite 빌드 파이프라인

3.1 build.rollupOptions로 청크 전략 잡기

프로덕션 성능의 핵심은 초기 JS 페이로드캐시 무효화 범위의 균형입니다. manualChunks로 벤더·라우트 단위를 나누되, 너무 잘게 쪼개면 HTTP/2에서도 오버헤드가 커질 수 있습니다.

// vite.config.ts (발췌)
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          if (id.includes('node_modules')) {
            if (id.includes('react-dom')) return 'vendor-react-dom';
            if (id.includes('react')) return 'vendor-react';
            return 'vendor';
          }
        },
      },
    },
  },
});

실무에서는 번들 분석기(rollup-plugin-visualizer 등) 로 실제 청크 크기를 본 뒤, 라이브러리별로 그룹을 조정합니다. 팀 내에서 “벤더는 장기 캐시, 앱 코드는 자주 배포” 같은 정책을 맞춰 두면 운영이 단순해집니다.

3.2 resolve.alias와 모노레포

모노레포에서 패키지 경로를 workspace:*로 묶을 때, Vite의 resolve.alias개발 서버와 빌드 모두에 영향을 줍니다. 조건부 exports 가 있는 패키지는 main/module만 보고 착각하기 쉬우므로, 실제 해석 결과를 vite --debug 또는 플러그인 로그로 확인하는 것이 좋습니다.


4. 커스텀 플러그인 개발

4.1 Vite 플러그인의 최소 골격

Vite 플러그인은 Rollup 호환 훅에 더해 configureServer, transformIndexHtml 등 개발 서버 전용 훅을 제공합니다. 아래는 가상 모듈을 노출하는 작은 예입니다.

// plugins/virtual-config.ts
import type { Plugin } from 'vite';

export function virtualConfigPlugin(data: Record<string, unknown>): Plugin {
  const virtualId = '\0virtual:app-config';

  return {
    name: 'virtual-app-config',
    resolveId(id) {
      if (id === 'virtual:app-config') return virtualId;
    },
    load(id) {
      if (id === virtualId) {
        return `export default ${JSON.stringify(data)};`;
      }
    },
  };
}

resolveId에서 \0 접두사를 쓰면 실제 파일 시스템과 충돌을 줄일 수 있습니다. 가상 모듈은 HMR 경로를 설계할 때 모듈 그래프 상 식별자로 취급해야 합니다.

4.2 enforceapply로 실행 순서 제어

  • enforce: 'pre' | 'post': 동일 훅 내에서 다른 플러그인보다 먼저/나중에 실행
  • apply: 'serve' | 'build' | ((config, env) => boolean): 개발 전용·빌드 전용 분리

예를 들어 개발 서버에서만 목(mock) 데이터를 주입하고, 프로덕션에서는 완전히 제거하려면 apply: 'serve'로 비용을 없앨 수 있습니다.

4.3 Rollup 플러그인 호환성 점검 체크리스트

  1. buildStart/generateBundle에서 부수 효과(파일 쓰기)가 개발 모드에도 돌지 않는지
  2. this.getModuleInfo 등 Rollup 4 API 변화에 맞는지
  3. CSS/에셋을 다루는 경우 Vite의 자산 URL 처리와 충돌하지 않는지

5. HMR 최적화 기법

5.1 병목: 의존성 사전 번들과 감시 범위

HMR이 느릴 때 흔한 원인은 거대한 node_modules 의존성이 매번 재처리되거나, 불필요하게 넓은 파일 감시입니다. optimizeDeps명시적 포함/제외를 지정하면 cold start와 재시작 이후 안정성이 좋아집니다.

// vite.config.ts (발췌)
export default defineConfig({
  optimizeDeps: {
    include: ['react', 'react-dom', 'scheduler'],
    exclude: ['큰-로컬-패키지-이름'],
  },
  server: {
    watch: {
      ignored: ['**/coverage/**', '**/dist/**'],
    },
  },
});

모노레포 루트에 로그·빌드 산출물이 많다면 watch.ignoredOS 파일 감시 한도에 걸리는 문제를 완화할 수 있습니다.

5.2 server.hmr와 네트워크 레이어

Docker, WSL2, 원격 컨테이너, HTTPS 프록시 뒤에서 개발할 때는 WebSocket 경로·포트·프로토콜 불일치로 HMR이 끊깁니다. 이때 server.hmr클라이언트가 접속해야 할 호스트/포트/프로토콜을 명시적으로 맞춥니다.

server: {
  host: true,
  port: 5173,
  strictPort: true,
  hmr: {
    protocol: 'wss',
    host: 'localhost',
    clientPort: 443,
  },
},

환경마다 값이 달라지면 환경 변수로 주입하고, 팀 문서에 프록시(Nginx, Caddy)의 Upgrade 헤더 설정을 함께 적어 두는 것이 좋습니다.

5.3 경계 모듈과 “풀 리로드” 최소화

순수 부수 효과가 큰 모듈(전역 스토어 초기화, 사이드 이펙트 import)은 HMR 시 풀 리로드로 떨어지기 쉽습니다. 상태 관리 라이브러리·컨텍스트 설계 시 모듈 단위로 경계를 나누고, 가능하면 import.meta.hot.accept로 국소 갱신을 허용하는 패턴을 고려합니다.

5.4 오버레이와 로그 소음

server.hmr.overlay로 런타임 에러 오버레이를 끄거나 줄일 수 있습니다. 다만 CI나 스토리북 환경에서는 에러를 숨기기보다 근본 원인을 제거하는 편이 안전합니다.


6. 프로덕션 빌드 최적화

6.1 압축기 선택: esbuild vs terser

Vite는 기본적으로 esbuild로 minify합니다. 호이스팅·특정 라이브러리와의 호환 이슈로 더 보수적인 압축이 필요하면 build.minify: 'terser'terserOptions를 검토합니다. 대신 빌드 시간이 늘어날 수 있으므로 CI에서 측정합니다.

6.2 CSS 코드 스플리팅과 assetsInlineLimit

build.cssCodeSplit은 라우트 단위 CSS 분리에 유리합니다. 작은 에셋은 Data URL 인라인으로 HTTP 요청 수를 줄이는데, build.assetsInlineLimit으로 기준을 조정합니다. 디자인 시스템 아이콘 SVG가 많다면 스프라이트·심볼 스택과 함께 전략을 통일합니다.

6.3 build.target과 폴리필

build.target은 출력 문법 수준을 결정합니다. 구형 브라우저 지원 범위가 넓다면 트랜스파일 부담이 커지므로, Browserslist·실 사용자 데이터를 기준으로 타깃을 좁히는 것이 우선입니다. 폴리필은 필요한 API만 core-js 등으로 채우고, 불필요한 전역 주입을 피합니다.

6.4 소스맵과 배포

build.sourcemap을 켜면 디버깅은 쉬워지지만 산출물 크기·배포 파이프라인이 무거워집니다. Sentry 등에 숨은 소스맵을 올리는 경우, 공개 디렉터리에 노출되지 않게 CI 단계를 분리합니다.


7. React·Vue·Svelte 통합

7.1 React: @vitejs/plugin-react와 SWC 옵션

공식 React 플러그인은 Fast Refresh를 지원합니다. 대규모 코드베이스에서는 @vitejs/plugin-react-swc 로 컴파일러 레이어를 바꿔 리프레시·빌드 시간을 줄이는 사례가 많습니다. 둘 다 쓸 수 없으므로 하나만 선택합니다.

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [['babel-plugin-styled-components', { displayName: true, ssr: false }]],
      },
    }),
  ],
});

스타일드 컴포넌트·emotion 등 Babel 플러그인이 필수라면 SWC 대신 Babel 경로를 유지하는 결정이 자연스럽습니다.

7.2 Vue: @vitejs/plugin-vue<script setup>

Vue SFC는 Vite와 궁합이 좋습니다. 타입 기반 defineProps 등 최신 문법을 쓸 때는 vue-tsc빌드 전 타입 검증을 병행합니다. 라우트 기반 코드 스플리팅은 import() 동적 로딩과 함께 청크 이름을 관찰합니다.

7.3 Svelte: vite-plugin-svelte와 전처리

SvelteKit이 아닌 순수 Vite + Svelte를 쓸 때는 vite-plugin-svelte 설정에서 전처리기(sass, postcss)HMR 안정성 옵션을 맞춥니다. 스타일 전역 오염을 막기 위해 컴포넌트 스코프 CSS 전략을 팀 규칙으로 두면 HMR 경로도 단순해집니다.

7.4 프레임워크 공통: SSR·미들웨어

Vite 자체는 SSR 가이드를 제공하지만, Next·Nuxt·SvelteKit 같은 프레임워크가 통합 스택을 제공하는 경우가 많습니다. “Vite만” 도입할지 “풀스택 프레임워크”를 쓸지에 따라 라우팅·데이터 로딩·환경 분리 책임이 달라지므로, 팀 역량과 배포 환경에 맞춰 선택합니다.


8. 트러블슈팅 요약

증상우선 확인
cold start 느림optimizeDeps, 사전 번들 캐시, 불필요한 플러그인
HMR 미작동프록시·WSS·server.hmr·방화벽
빌드 OOM청크 분할, build.rollupOptions, Node 메모리 플래그
해석 오류resolve.dedupe, 패키지 exports, alias 충돌

9. 맺음말

Vite 5는 개발 경험과 프로덕션 번들을 명확히 분리된 두 레이어로 다루는 도구입니다. Rollup 4 기반 빌드, loadEnv를 통한 환경 주입, 플러그인의 apply/enforce, HMR 네트워크·사전 번들 튜닝, 프레임워크 플러그인 선택을 한 흐름으로 설계하면, 팀 전체의 빌드 시간과 디버깅 비용을 동시에 줄일 수 있습니다. 변경이 클 때는 마이그레이션 문서·번들 리포트·실제 배포 스테이징 세 가지를 항상 세트로 두시기 바랍니다.

배포 전에는 git add 후 커밋·푸시를 마친 뒤, 이 저장소 관례에 따라 npm run deploy를 실행하세요.

심화 부록: 구현·운영 관점

이 부록은 앞선 본문에서 다룬 주제(「Vite 5 고급 가이드 — Rollup·플러그인·HMR·프로덕션 최적화」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.

내부 동작과 핵심 메커니즘

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]
sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(런타임·게이트웨이·프로세스)
  participant D as 의존성(API·DB·큐·파일)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)
  • 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.

프로덕션 운영 패턴

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가
용량피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.

확장 예시: 엔드투엔드 미니 시나리오

앞선 본문 주제(「Vite 5 고급 가이드 — Rollup·플러그인·HMR·프로덕션 최적화」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)
  authorize(validated, ctx)
  result = domainCore(validated)
  persistOrEmit(result, idempotentKey)
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정 불일치프로필·시크릿·기본값, 리전스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

배포 전에는 git addgit commitgit pushnpm run deploy 순서를 권장합니다.


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Vite 5 프로덕션 빌드(Rollup 4), 환경별 설정, 커스텀 플러그인, HMR 튜닝, React·Vue·Svelte 통합까지 실무 최적화를 정리한 고급 가이드입니다. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

Vite, Build Tool, Performance, HMR, Plugin 등으로 검색하시면 이 글이 도움이 됩니다.