Vite 완벽 가이드: 차세대 프론트엔드 빌드 도구

Vite 완벽 가이드: 차세대 프론트엔드 빌드 도구

이 글의 핵심

Vite는 ESBuild 기반의 초고속 개발 서버와 Rollup 기반의 최적화된 프로덕션 빌드를 제공하는 차세대 빌드 도구입니다. 네이티브 ESM을 활용하여 번들링 없이 즉시 서버를 시작하고, HMR로 빠른 피드백 루프를 제공합니다. Webpack 대비 10-100배 빠른 콜드 스타트와 거의 즉각적인 HMR 업데이트를 경험할 수 있습니다.

Vite란?

Vite(프랑스어로 ‘빠른’이라는 뜻)는 Evan You(Vue.js 창시자)가 만든 차세대 프론트엔드 빌드 도구입니다. 네이티브 ES 모듈을 활용하여 번들러 없이 즉시 개발 서버를 시작하고, 프로덕션에서는 Rollup으로 최적화된 번들을 생성합니다.

핵심 특징

  1. 초고속 개발 서버

    • 콜드 스타트 시간 무시 가능
    • 네이티브 ESM 활용
    • 번들링 없음
  2. 즉각적인 HMR

    • 파일 크기와 무관
    • 모듈 단위 업데이트
    • 상태 유지
  3. 최적화된 빌드

    • Rollup 기반
    • Tree-shaking
    • Code splitting
  4. 풍부한 기능

    • TypeScript 지원
    • JSX/TSX 지원
    • CSS 전처리기
    • 플러그인 생태계

Webpack vs Vite 비교

개발 모드

Webpack (Bundle-based Dev Server)
┌─────────────────────────────────┐
│  Entry ──→ [Bundling] ──→ Server │
│  (시작에 수십 초 ~ 분 소요)        │
└─────────────────────────────────┘

Vite (Native ESM-based Dev Server)
┌─────────────────────────┐
│  Entry ──→ Server (즉시) │
│  (네이티브 ESM 제공)      │
└─────────────────────────┘

성능 비교

항목WebpackVite
콜드 스타트30-60초1-2초
HMR 업데이트3-10초50-200ms
빌드 시간분 단위초 단위
설정 복잡도높음낮음
학습 곡선가파름완만함

프로젝트 생성

CLI로 생성

# npm
npm create vite@latest

# yarn
yarn create vite

# pnpm
pnpm create vite

# 템플릿 직접 지정
npm create vite@latest my-app -- --template react
npm create vite@latest my-app -- --template vue
npm create vite@latest my-app -- --template svelte
npm create vite@latest my-app -- --template solid
npm create vite@latest my-app -- --template preact

# TypeScript 템플릿
npm create vite@latest my-app -- --template react-ts
npm create vite@latest my-app -- --template vue-ts

수동 설정

# 프로젝트 생성
mkdir my-vite-app && cd my-vite-app
npm init -y

# Vite 설치
npm install -D vite

# React 설치 (예시)
npm install react react-dom
npm install -D @vitejs/plugin-react

# TypeScript 설치 (옵션)
npm install -D typescript @types/react @types/react-dom

프로젝트 구조

my-vite-app/
├── index.html          # 진입점 (public이 아님!)
├── vite.config.js
├── package.json
├── src/
│   ├── main.jsx
│   ├── App.jsx
│   └── assets/
├── public/             # 정적 파일
└── dist/               # 빌드 결과물

기본 설정

vite.config.js (React)

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

export default defineConfig({
  plugins: [react()],
  
  // 개발 서버 설정
  server: {
    port: 3000,
    open: true,  // 자동으로 브라우저 열기
    host: true,  // 네트워크 접근 허용
  },
  
  // 빌드 설정
  build: {
    outDir: 'dist',
    sourcemap: true,
    minify: 'esbuild',  // 또는 'terser'
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
        },
      },
    },
  },
  
  // 경로 별칭
  resolve: {
    alias: {
      '@': '/src',
      '@components': '/src/components',
      '@utils': '/src/utils',
    },
  },
  
  // 환경 변수
  envPrefix: 'VITE_',
});

vite.config.ts (TypeScript)

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

export default defineConfig({
  plugins: [react()],
  
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
  
  server: {
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://localhost:5000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
    },
  },
});

환경 변수

.env 파일

# .env - 모든 환경
VITE_APP_TITLE=My App

# .env.local - 로컬 (gitignore)
VITE_API_KEY=secret-key

# .env.development - 개발
VITE_API_URL=http://localhost:5000

# .env.production - 프로덕션
VITE_API_URL=https://api.example.com

사용법

// TypeScript에서 타입 정의
/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_APP_TITLE: string;
  readonly VITE_API_URL: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

// 사용
const apiUrl = import.meta.env.VITE_API_URL;
const title = import.meta.env.VITE_APP_TITLE;

// 모드 확인
const isDev = import.meta.env.DEV;
const isProd = import.meta.env.PROD;
const mode = import.meta.env.MODE;

정적 에셋 처리

이미지 import

// URL로 import
import logo from './logo.svg';

function App() {
  return <img src={logo} alt="Logo" />;
}

// 동적 import
const imagePath = '/images/photo.jpg';
<img src={imagePath} alt="Photo" />

// public 폴더 (빌드 시 그대로 복사)
<img src="/favicon.ico" alt="Favicon" />

?url, ?raw 쿼리

// URL 문자열로 가져오기
import imageUrl from './image.png?url';

// 원본 텍스트로 가져오기
import shaderCode from './shader.glsl?raw';

// Worker로 가져오기
import Worker from './worker.js?worker';
const worker = new Worker();

CSS 처리

// CSS import
import './styles.css';

// CSS Modules
import styles from './App.module.css';
<div className={styles.container}>...</div>

// SCSS/SASS
import './styles.scss';

// CSS-in-JS (styled-components, emotion 등)
// 플러그인 필요

플러그인

React 플러그인

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

export default defineConfig({
  plugins: [
    react({
      // Fast Refresh 옵션
      fastRefresh: true,
      
      // Babel 옵션
      babel: {
        plugins: ['babel-plugin-styled-components'],
      },
    }),
  ],
});

Vue 플러그인

npm install -D @vitejs/plugin-vue
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          isCustomElement: (tag) => tag.startsWith('ion-'),
        },
      },
    }),
  ],
});

PWA 플러그인

npm install -D vite-plugin-pwa
import { defineConfig } from 'vite';
import { VitePWA } from 'vite-plugin-pwa';

export default defineConfig({
  plugins: [
    VitePWA({
      registerType: 'autoUpdate',
      manifest: {
        name: 'My App',
        short_name: 'App',
        theme_color: '#ffffff',
        icons: [
          {
            src: '/icon-192.png',
            sizes: '192x192',
            type: 'image/png',
          },
          {
            src: '/icon-512.png',
            sizes: '512x512',
            type: 'image/png',
          },
        ],
      },
    }),
  ],
});

기타 유용한 플러그인

# SVG를 React 컴포넌트로
npm install -D vite-plugin-svgr

# 환경 변수 타입 생성
npm install -D vite-plugin-environment

# Bundle 분석
npm install -D rollup-plugin-visualizer

# 컴프레션
npm install -D vite-plugin-compression
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import svgr from 'vite-plugin-svgr';
import { visualizer } from 'rollup-plugin-visualizer';
import compression from 'vite-plugin-compression';

export default defineConfig({
  plugins: [
    react(),
    svgr(),
    visualizer({ open: true }),
    compression({ algorithm: 'gzip' }),
  ],
});

프록시 설정

// vite.config.js
export default defineConfig({
  server: {
    proxy: {
      // 문자열 단축 표기
      '/foo': 'http://localhost:4567',
      
      // 옵션 사용
      '/api': {
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
      
      // 정규식 사용
      '^/fallback/.*': {
        target: 'http://jsonplaceholder.typicode.com',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/fallback/, ''),
      },
      
      // WebSocket 프록시
      '/socket.io': {
        target: 'ws://localhost:5174',
        ws: true,
      },
    },
  },
});

TypeScript 설정

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,

    /* Path Alias */
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"]
    }
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

vite-env.d.ts

/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_APP_TITLE: string;
  readonly VITE_API_URL: string;
  // 추가 환경 변수...
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

// CSS Modules 타입
declare module '*.module.css' {
  const classes: { [key: string]: string };
  export default classes;
}

declare module '*.module.scss' {
  const classes: { [key: string]: string };
  export default classes;
}

// 이미지 타입
declare module '*.svg' {
  import * as React from 'react';
  export const ReactComponent: React.FunctionComponent<
    React.SVGProps<SVGSVGElement> & { title?: string }
  >;
  const src: string;
  export default src;
}

프로덕션 빌드 최적화

Code Splitting

// vite.config.js
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          // 벤더 청크 분리
          vendor: ['react', 'react-dom'],
          ui: ['@mui/material', '@emotion/react'],
          utils: ['lodash', 'axios'],
        },
        
        // 청크 파일명 패턴
        chunkFileNames: 'js/[name]-[hash].js',
        entryFileNames: 'js/[name]-[hash].js',
        assetFileNames: '[ext]/[name]-[hash].[ext]',
      },
    },
    
    // 청크 크기 경고 임계값
    chunkSizeWarningLimit: 1000,
  },
});

동적 Import

import { lazy, Suspense } from 'react';

// 컴포넌트 lazy loading
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/profile" element={<Profile />} />
      </Routes>
    </Suspense>
  );
}

빌드 분석

import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
  plugins: [
    visualizer({
      open: true,
      gzipSize: true,
      brotliSize: true,
    }),
  ],
});

Preload & Prefetch

<!-- index.html -->
<head>
  <!-- 중요 리소스 preload -->
  <link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
  
  <!-- 향후 필요한 리소스 prefetch -->
  <link rel="prefetch" href="/js/dashboard-[hash].js">
</head>

SSR (Server-Side Rendering)

서버 진입점

// server.js
import express from 'express';
import { createServer as createViteServer } from 'vite';

async function createServer() {
  const app = express();

  // Vite를 미들웨어로 사용
  const vite = await createViteServer({
    server: { middlewareMode: true },
    appType: 'custom',
  });

  app.use(vite.middlewares);

  app.use('*', async (req, res) => {
    try {
      const url = req.originalUrl;

      // HTML 템플릿 로드
      let template = await vite.transformIndexHtml(
        url,
        `<!DOCTYPE html>
        <html>
          <head><title>SSR App</title></head>
          <body>
            <div id="app"><!--ssr-outlet--></div>
            <script type="module" src="/src/entry-client.jsx"></script>
          </body>
        </html>`
      );

      // 서버 진입점 로드
      const { render } = await vite.ssrLoadModule('/src/entry-server.jsx');

      // 앱 렌더링
      const appHtml = await render(url);

      // HTML에 삽입
      const html = template.replace(`<!--ssr-outlet-->`, appHtml);

      res.status(200).set({ 'Content-Type': 'text/html' }).end(html);
    } catch (e) {
      vite.ssrFixStacktrace(e);
      console.error(e);
      res.status(500).end(e.message);
    }
  });

  app.listen(3000);
}

createServer();

성능 팁

1. 의존성 사전 번들링

// vite.config.js
export default defineConfig({
  optimizeDeps: {
    include: ['react', 'react-dom'],  // 강제 포함
    exclude: ['@your-local-package'],  // 제외
  },
});

2. 빌드 시간 단축

export default defineConfig({
  build: {
    // terser 대신 esbuild (더 빠름)
    minify: 'esbuild',
    
    // sourcemap 비활성화 (프로덕션)
    sourcemap: false,
    
    // CSS 코드 분할 비활성화 (작은 프로젝트)
    cssCodeSplit: false,
  },
});

3. 개발 서버 최적화

export default defineConfig({
  server: {
    // 파일 시스템 캐싱
    fs: {
      strict: false,
      allow: ['..'],
    },
    
    // HMR 최적화
    hmr: {
      overlay: false,  // 에러 오버레이 비활성화
    },
  },
});

마이그레이션 (Webpack → Vite)

1. 의존성 교체

# 제거
npm uninstall webpack webpack-cli webpack-dev-server
npm uninstall html-webpack-plugin css-loader style-loader

# 설치
npm install -D vite @vitejs/plugin-react

2. index.html 이동

# Before (Webpack)
public/index.html

# After (Vite)
index.html  # 루트로 이동

3. 환경 변수 변경

// Before (Webpack)
process.env.REACT_APP_API_URL

// After (Vite)
import.meta.env.VITE_API_URL

4. import 경로 수정

// Before (Webpack) - require()
const config = require('./config.json');

// After (Vite) - import
import config from './config.json';

// SVG 처리
// Before
import logo from './logo.svg';

// After (with vite-plugin-svgr)
import { ReactComponent as Logo } from './logo.svg';

트러블슈팅

Module not found

// 경로 별칭 설정
export default defineConfig({
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
});

CORS 에러

// 프록시 설정
export default defineConfig({
  server: {
    proxy: {
      '/api': 'http://localhost:5000',
    },
  },
});

느린 초기 로딩

// 의존성 사전 번들링 강제
export default defineConfig({
  optimizeDeps: {
    force: true,
  },
});

Vite는 빠르고 현대적인 프론트엔드 개발 경험을 제공합니다. 즉각적인 서버 시작과 HMR로 생산성을 크게 향상시킬 수 있습니다.