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 を再代入せず、必ずミューテーションすること)
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の原因です。
// 悪い例 — 1つのユーティリティをインポートするためにバレル全体が読み込まれる
import { slash } from '@/utils'
// 良い例 — 直接インポート、該当ファイルのみが読み込まれる
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ライブラリは両者で動作が異なる場合があります。デプロイ前に必ず 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: ''
// 悪い例:require() が Vite で動作すると想定している
トラブルシューティングの流れ
flowchart TD
A[問題の特定] --> B{エラーの種類は?}
B -->|ビルドエラー| C[vite build の出力を確認]
B -->|開発サーバーエラー| D[ターミナルログを確認]
B -->|HMRが動作しない| E[プラグイン設定を確認]
C --> F{エラーが発生?}
F -->|Yes| G[依存関係と設定を確認]
F -->|No| H[本番環境でテスト]
D --> I{エラーが発生?}
I -->|Yes| J[プラグインとプロキシ設定を確認]
I -->|No| K[正常に動作]