Vite パターン
Vite 8+ プロジェクト向けのビルドツールおよび開発サーバーのパターン集。設定、環境変数、プロキシ設定、ライブラリモード、依存関係の事前バンドル、および一般的な本番環境の落とし穴をカバーします。
使用タイミング
vite.config.tsまたはvite.config.jsを設定する場合- 環境変数または
.envファイルを設定する場合 - API バックエンド用の開発サーバープロキシを設定する場合
- ビルド出力(チャンク、ミニファイ、アセット)を最適化する場合
build.libを使用してライブラリを公開する場合- 依存関係の事前バンドルまたは CJS/ESM 相互運用に関するトラブルシューティングを行う場合
- HMR、開発サーバー、またはビルドエラーをデバッグする場合
- Vite プラグインの選択または順序付けを行う場合
仕組み
- 開発モード では、ソースファイルをネイティブ ESM として提供します。バンドルは行われません。変換はモジュールリクエストごとにオンデマンドで実行されるため、コールドスタートが高速で HMR が正確です。
- ビルドモード では、Rolldown(v7+)または Rollup(v5~v6)を使用して、ツリーシェイキング、コード分割、Oxc ベースのミニファイを備えたプロダクションバンドルを作成します。
- 依存関係の事前バンドル は、CJS/UMD の依存関係を esbuild を使用して一度 ESM に変換し、結果を
node_modules/.viteにキャッシュするため、以降の起動ではこの処理がスキップされます。 - プラグイン は、開発とビルドで統一されたインターフェースを共有します。同じプラグインオブジェクトが、開発サーバーのオンデマンド変換とプロダクションパイプラインの両方で機能します。
- 環境変数 は、ビルド時に静的にインライン化されます。
VITE_プレフィックスが付いた変数は、バンドル内で公開定数になります。プレフィックスがないものは、クライアントコードからは見えません。
例
設定構造
基本設定
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
resolve: {
alias: { '@': new URL('./src', import.meta.url).pathname },
},
})
条件付き設定
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd()) // VITE_ プレフィックスのみ(安全)
return {
plugins: [react()],
server: command === 'serve' ? { port: 3000 } : undefined,
define: {
__API_URL__: JSON.stringify(env.VITE_API_URL),
},
}
})
主要な設定オプション
| キー | デフォルト | 説明 |
|---|---|---|
root | '.' | プロジェクトルート(index.html がある場所) |
base | '/' | デプロイされたアセットの公開ベースパス |
envPrefix | 'VITE_' | クライアント公開の環境変数のプレフィックス |
build.outDir | 'dist' | 出力ディレクトリ |
build.minify | 'oxc' | ミニファイア('oxc'、'terser'、または false) |
build.sourcemap | false | true、'inline'、または 'hidden' |
プラグイン
必須プラグイン
ほとんどのプラグインニーズは、いくつかのメンテナンスが行き届いたパッケージでカバーされます。独自のプラグインを作成する前に、これらを試してください。
| プラグイン | 目的 | 使用タイミング |
|---|---|---|
@vitejs/plugin-react-swc | SWC による React HMR + Fast Refresh | React アプリのデフォルト(Babel 版より高速) |
@vitejs/plugin-react | Babel による React HMR + Fast Refresh | Babel プラグインが必要な場合のみ(emotion、MobX デコレータ) |
@vitejs/plugin-vue | Vue 3 SFC サポート | Vue アプリ |
vite-plugin-checker | ワーカースレッドで tsc + ESLint を実行し、HMR オーバーレイを表示 | TypeScript アプリ全般 — Vite は vite build 時に型チェックを行いません |
vite-tsconfig-paths | tsconfig.json の paths エイリアスを尊重 | tsconfig.json にエイリアスが既にある場合 |
vite-plugin-dts | ライブラリモードで .d.ts ファイルを生成 | TypeScript ライブラリを公開する場合 |
vite-plugin-svgr | SVG を React コンポーネントとしてインポート | SVG をコンポーネントとして使用する React アプリ |
rollup-plugin-visualizer | バンドルのツリーマップ/サンバーストレポート | 定期的なバンドルサイズ監査(enforce: 'post' を使用) |
vite-plugin-pwa | ゼロ設定の PWA + Workbox | オフライン対応アプリ |
重要な注意: vite build はトランスパイルを行いますが、型チェックは行いません。vite-plugin-checker を追加するか、CI で tsc --noEmit を実行しない限り、型エラーはそのまま本番環境に出荷されます。
カスタムプラグインの作成
カスタムプラグインを作成することは稀です。ほとんどのニーズは既存のプラグインでカバーされます。必要な場合は、まず vite.config.ts にインラインで記述し、再利用する場合にのみ分離します。
// vite.config.ts — 最小限のインラインプラグイン
function myPlugin(): Plugin {
return {
name: 'my-plugin', // 必須、一意である必要があります
enforce: 'pre', // 'pre' | 'post'(オプション)
apply: 'build', // 'build' | 'serve'(オプション)
transform(code, id) {
if (!id.endsWith('.custom')) return
return { code: transformCustom(code), map: null }
},
}
}
主要なフック: transform(ソースの変更)、resolveId + load(仮想モジュール)、transformIndexHtml(HTML への注入)、configureServer(開発ミドルウェアの追加)、hotUpdate(カスタム HMR — v7+ で非推奨となった handleHotUpdate を置き換え)。
仮想モジュール は \0 プレフィックス規則を使用します。resolveId が '\0virtual:my-id' を返すため、他のプラグインはこれをスキップします。ユーザーコードは 'virtual:my-id' をインポートします。
完全なプラグイン API については、vite.dev/guide/api-plugin を参照してください。開発中は vite-plugin-inspect を使用して変換パイプラインをデバッグします。
HMR API
フレームワークプラグイン(@vitejs/plugin-react、@vitejs/plugin-vue など)は HMR を自動的に処理します。カスタム状態ストア、開発ツール、またはフレームワークに依存しないユーティリティを構築し、更新間で状態を維持する必要がある場合にのみ、import.meta.hot を直接使用してください。
// src/store.ts — バニラモジュールの手動 HMR
if (import.meta.hot) {
// 更新間で状態を維持(.data を再代入せず、必ず MUTATE すること)
import.meta.hot.data.count = import.meta.hot.data.count ?? 0
// モジュールが置き換えられる前に副作用をクリーンアップ
import.meta.hot.dispose((data) => clearInterval(data.intervalId))
// このモジュール自身の更新を受け入れる
import.meta.hot.accept()
}
すべての import.meta.hot コードはプロダクションビルドからツリーシェイキングされるため、ガードの削除は必要ありません。
環境変数
Vite は .env、.env.local、.env.[mode]、.env.[mode].local をこの順序で読み込みます(後で上書き)。*.local ファイルは gitignore され、ローカルシークレット用です。
クライアントサイドからのアクセス
VITE_ プレフィックスが付いた変数のみがクライアントコードに公開されます。
import.meta.env.VITE_API_URL // 文字列
import.meta.env.MODE // 'development' | 'production' | カスタム
import.meta.env.BASE_URL // base 設定値
import.meta.env.DEV // 真偽値
import.meta.env.PROD // 真偽値
import.meta.env.SSR // 真偽値
設定内で環境変数を使用する
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd()) // VITE_ プレフィックスのみ(安全)
return {
define: {
__API_URL__: JSON.stringify(env.VITE_API_URL),
},
}
})
セキュリティ
VITE_ プレフィックスはセキュリティ境界ではありません
VITE_ プレフィックスが付いた変数はすべて、ビルド時に クライアントバンドルに静的にインライン化されます。ミニファイ、Base64 エンコード、ソースマップの無効化では隠せません。決意のある攻撃者は、出荷された JavaScript から任意の VITE_ 変数を抽出できます。
ルール: 公開値(API URL、機能フラグ、公開鍵)のみを VITE_ 変数に格納します。シークレット(API トークン、データベース URL、秘密鍵)は、API またはサーバーレス関数の背後でサーバーサイドに置く必要があります。
loadEnv('') の罠
// 悪い例: 第3引数に '' を渡すと、すべての環境変数(サーバーシークレットを含む)が読み込まれ、
// `define` を介してクライアントコードにインライン化できるようになります。
const env = loadEnv(mode, process.cwd(), '')
// 良い例: 明示的なプレフィックスリスト
const env = loadEnv(mode, process.cwd(), ['VITE_', 'APP_'])
本番環境のソースマップ
本番環境のソースマップは、元のソースコードを漏洩させます。エラートラッカー(Sentry、Bugsnag)にアップロードしてローカルで削除する場合を除き、無効にしてください。
build: {
sourcemap: false, // デフォルト — このまま維持
}
.gitignore チェックリスト
.env.local、.env.*.local— ローカルシークレットの上書きdist/— ビルド出力node_modules/.vite— 事前バンドルキャッシュ(古いエントリが原因で誤ったエラーが発生する可能性があります)
サーバープロキシ
// vite.config.ts — server.proxy
server: {
proxy: {
'/foo': 'http://localhost:4567', // 文字列の短縮形
'/api': {
target: 'http://localhost:8080',
changeOrigin: true, // 仮想ホストバックエンドに必要
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
}
WebSocket プロキシの場合は、ルート設定に ws: true を追加します。
ビルド最適化
手動チャンク分割
// vite.config.ts — build.rolldownOptions
build: {
rolldownOptions: {
output: {
// オブジェクト形式: 特定のパッケージをグループ化
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-popover'],
},
},
},
}
// 関数形式: ヒューリスティックで分割
manualChunks(id) {
if (id.includes('node_modules/react')) return 'react-vendor'
if (id.includes('node_modules')) return 'vendor'
}
パフォーマンス
バレルファイルを避ける
バレルファイル(index.ts がディレクトリ内のすべてを再エクスポートする)は、単一のシンボルをインポートする場合でも、Vite に再エクスポートされたすべてのファイルを強制的にロードさせます。これは、公式ドキュメントで指摘されている開発サーバー速度低下の最大の原因です。
// 悪い例 — 1つのユーティリティをインポートするだけで、バレル全体をロードする
import { slash } from '@/utils'
// 良い例 — 直接インポート、1つのファイルのみがロードされる
import { slash } from '@/utils/slash'
インポート拡張子を明示する
暗黙の拡張子ごとに、resolve.extensions を介して最大6回のファイルシステムチェックが強制されます。大規模なコードベースでは、これが積み重なります。
// 悪い例
import Component from './Component'
// 良い例
import Component from './Component.tsx'
tsconfig.json の allowImportingTsExtensions と resolve.extensions を、実際に使用する拡張子のみに絞り込みます。
ホットパスルートをウォームアップする
server.warmup.clientFiles は、ブラウザがリクエストする前に、既知のホットエントリを事前変換します。これにより、大規模アプリでのコールドロードのリクエストウォーターフォールが解消されます。
// vite.config.ts
server: {
warmup: {
clientFiles: ['./src/main.tsx', './src/routes/**/*.tsx'],
},
}
遅い開発サーバーのプロファイリング
vite dev が遅いと感じたら、vite --profile で開始し、アプリを操作してから p+enter を押して .cpuprofile を保存します。Speedscope にロードして、どのプラグインが時間を消費しているか(通常はコミュニティプラグインの buildStart、config、または configResolved フック)を特定します。
ライブラリモード
npm パッケージを公開する場合は、build.lib を使用します。設定の詳細よりも、2つの落とし穴が重要です。
- 型は出力されません —
vite-plugin-dtsを追加するか、tsc --emitDeclarationOnlyを個別に実行します。 - ピア依存関係は必ず外部化する必要があります — リストにないピアはライブラリにバンドルされ、コンシューマーで重複ランタイムエラーが発生します。
// vite.config.ts
build: {
lib: {
entry: 'src/index.ts',
formats: ['es', 'cjs'],
fileName: (format) => `my-lib.${format}.js`,
},
rolldownOptions: {
external: ['react', 'react-dom', 'react/jsx-runtime'], // すべてのピア依存関係
},
}
SSR 外部化
ベアの createServer({ middlewareMode: true }) セットアップは、フレームワーク作成者の領域です。ほとんどのアプリでは、代わりに Nuxt、Remix、SvelteKit、Astro、または TanStack Start を使用する必要があります。フレームワークユーザーとして調整するのは、依存関係が SSR で壊れた場合の外部化設定です。
// vite.config.ts — ssr オプション
ssr: {
external: ['node-native-package'], // SSR バンドルでは require() として保持
noExternal: ['esm-only-package'], // SSR 出力に強制バンドル(ほとんどの SSR エラーを修正)
target: 'node', // 'node' または 'webworker'
}
依存関係の事前バンドル
Vite は依存関係を事前バンドルして、CJS/UMD を ESM に変換し、リクエスト数を削減します。
// vite.config.ts — optimizeDeps
optimizeDeps: {
include: [
'lodash-es', // 既知の重い依存関係を強制的に事前バンドル
'cjs-package', // 相互運用の問題を引き起こす CJS 依存関係
'deep-lib/components/**', // 深いインポートのグロブ
],
exclude: ['local-esm-package'], // 除外する場合は有効な ESM である必要があります
force: true, // キャッシュを無視して再最適化(一時的なデバッグ用)
}
よくある落とし穴
開発環境とビルド環境の不一致
開発環境では esbuild/Rolldown が変換に使用され、ビルド環境では Rolldown がバンドルに使用されます。CJS ライブラリは、2つの間で動作が異なる場合があります。デプロイ前に必ず vite build && vite preview で確認してください。
デプロイ後の古いチャンク
新しいビルドは新しいチャンクハッシュを生成します。アクティブなセッションを持つユーザーは、もう存在しない古いファイル名をリクエストします。Vite には組み込みの解決策はありません。軽減策:
- デプロイウィンドウの間、古い
dist/assets/ファイルを存続させる - ルーターで動的インポートエラーをキャッチし、ページを強制リロードする
Docker とコンテナ
Vite はデフォルトで localhost にバインドするため、コンテナの外部からは到達できません。
// vite.config.ts — Docker/コンテナ設定
server: {
host: true, // 0.0.0.0 にバインド
hmr: { clientPort: 3000 }, // リバースプロキシの背後にある場合
}
モノレポのファイルアクセス
Vite はファイルの提供をプロジェクトルートに制限します。ルート外のパッケージはブロックされます。
// vite.config.ts — モノレポファイルアクセス
server: {
fs: {
allow: ['..'], // 親ディレクトリ(ワークスペースルート)を許可
},
}
アンチパターン
// 悪い例: envPrefix を '' に設定すると、すべての環境変数(シークレットを含む)がクライアントに公開される
envPrefix: ''
// 悪い例: Vite のブラウザコードで require() が機能すると仮定する