【実務・中級編】Viteの『Runtime Injection』で実現するビルドレスな設定切り替え:環境変数をビルド後に動的置換する裏技 – ビルド・パッケージ管理ツール生産性向上バイブル

こんにちは。テックリードの私だ。

日々のフロントエンド開発において、Viteの爆速なHMR(Hot Module Replacement)やESMベースのビルドスピードには、誰もが恩恵を受けていることだろう。だが、プロジェクトが成長し、KubernetesやECSといったコンテナオーケストレーション環境でのデプロイパイプラインを構築するフェーズに突入した瞬間、誰もがこの「呪い」に直面する。

「`import.meta.env` は、ビルド時にコードへハードコードされる」

この仕様の何が問題か。CI/CDパイプラインにおいて、ステージング環境用と本番環境用のビルド成果物(`dist`ディレクトリ)を別々にビルドしていればまだいい。しかし、クラウドネイティブの鉄則は「Build Once, Deploy Anywhere(一度ビルドしたコンテナを、全環境で使い回す)」だ。

環境変数を変えるたびに数分をかけてDockerイメージを再ビルドし、レジストリにプッシュし直す――そんな非効率なワークフローに開発チームの時間をドブに捨てさせていないか?

今回は、Viteのビルド機構の裏をかき、「ビルド後の成果物に対して、コンテナ起動時(Runtime)に環境変数を動的インジェクションする」という、実務で即座に使えるアーキテクチャの裏技を完全解説しよう。

—

1. なぜ「ビルド時環境変数」のままだと破綻するのか

通常のViteアプリケーションでは、`.env.production`などに記述した変数は、`vite build`の実行瞬間にコード内のプレースホルダーへ直接埋め込まれる(あるいは文字列置換される)。

// ビルド後のJSファイル内(イメージ)
console.log(“https://api.staging.example.com”); // ← ここが固定されてしまう

これでは、同じDockerイメージを `staging` クラスターから `production` クラスターへプロモーションした際、APIの向き先を切り替えることができない。環境変数ごとにイメージを作るということは、ビルドのたびにトランスパイルとバンドル処理のコストが発生し、CIのランニングコストを圧迫するだけでなく、「本当に同じバイナリ(成果物)が本番にいるのか」というトレーサビリティの担保すら危うくなる。

この制約を突破するには、「ビルド時はプレースホルダーを置いておき、コンテナの起動スクリプト(エントリーポイント)で実際の環境変数に書き換える」というランタイム・インジェクションの思想が必要不可欠となる。

—

2. アーキテクチャの全体像:Runtime Injectionの仕組み

今回構築する仕組みの流れはこうだ。

1. Viteの設定: ビルド時に変数領域を空っぽ、あるいはグローバル変数への参照にしておく。
2. HTMLインジェクション: `index.html` の `` 内に、実行時設定を格納する専用の `



ステップ3: コンテナ起動時のインジェクション・シェルスクリプト

Dockerイメージには静的なNginxとビルド済み成果物だけを同梱する。そして、コンテナが起動した瞬間に実行される `docker-entrypoint.sh` を用意する。このスクリプトが、環境変数(例: `API_BASE_URL`)を拾い、HTML内のプレースホルダーを書き換える。

`docker-entrypoint.sh`

!/bin/sh
エラーが発生した時点でスクリプトを即座に終了する
set -e

デフォルト値の設定(環境変数が未設定の場合のフォールバック)
API_BASE_URL=${API_BASE_URL:-"https://api.default.example.com"}
FEATURE_FLAG_NEW_UI=${FEATURE_FLAG_NEW_UI:-"false"}

Nginxのドキュメントルート配下にあるすべてのHTMLファイルを対象に置換処理を実行
sedを用いて、プレースホルダー文字列を実際の環境変数にインプレース(上書き)置換する
find /usr/share/nginx/html -type f -name ".html" | while read -r file; do
echo "Injecting runtime environment into: $file"

# envsubstを使う方法もあるが、sedの区切り文字(#)を使うことでURLのスラッシュ衝突を防ぐ
sed -i "s|__VITE_PLACEHOLDER_API_BASE_URL__|${API_BASE_URL}|g" "$file"
sed -i "s|__VITE_PLACEHOLDER_FEATURE_FLAG__|${FEATURE_FLAG_NEW_UI}|g" "$file"
done

DockerのCMDで指定されたプロセス(Nginxなど)に処理を移譲する
exec "$@"

ステップ4: Dockerfileの構成

マルチステージビルドを駆使し、最終的なイメージを極限まで軽量かつセキュアに保つ。

`Dockerfile`

--- ビルドステージ ---
Node.js環境でViteのビルドを実行する
FROM node:20-alpine AS builder

WORKDIR /app

依存関係のキャッシュ効率を最大化するため、package.jsonを先にコピー
COPY package.json package-lock.json ./
RUN npm ci

ソースコードをコピーしてビルドを実行
COPY . .
RUN npm run build

--- ランタイムステージ ---
超軽量なNginx公式イメージを採用
FROM nginx:alpine-slim

ビルドステージから成果物(dist)をNginxの公開ディレクトリへコピー
COPY --from=builder /app/dist /usr/share/nginx/html

ホスト側から用意したエントリーポイントスクリプトをコンテナに配置
COPY docker-entrypoint.sh /docker-entrypoint.sh

スクリプトに実行権限を付与
RUN chmod +x /docker-entrypoint.sh

ポートの公開
EXPOSE 80

コンテナ起動時に必ずエントリーポイントスクリプトを経由させる
ENTRYPOINT ["/docker-entrypoint.sh"]

Nginxをフォアグラウンドで起動
CMD ["nginx", "-g", "daemon off;"]

---

4. チーム開発を加速させる「神プラグイン」と「設定共有化ルール」

このアーキテクチャをチームに導入する際、ローカル開発(`vite dev`)と本番環境(Docker)で設定の挙動が乖離してはならない。ここでは、開発効率を爆上げする実践的知見を共有しよう。

おすすめViteプラグイン: `vite-plugin-html`

HTML内のプレースホルダー置換をローカル開発時やビルド時にもエミュレートしたい場合、`vite-plugin-html`などのインジェクション系プラグインを活用するか、独自の簡易プラグインを `vite.config.ts` に直接書くのがスマートだ。

`vite.config.ts` のカスタムインジェクション例:

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

export default defineConfig({
plugins: [
vue(),
{
name: 'html-runtime-config-transform',
transformIndexHtml(html) {
// ローカル開発時にダミーの環境変数を埋め込むカスタムフック
return html
.replace('__VITE_PLACEHOLDER_API_BASE_URL__', 'http://localhost:8080')
.replace('__VITE_PLACEHOLDER_FEATURE_FLAG__', 'true');
},
},
],
});

チーム開発のルール:設定の型とバリデーション

環境変数の渡し忘れやタイポによる「白画面バグ」を防ぐため、アプリ起動時(`src/main.ts` の最上部など)に、注入された設定値の簡易バリデーション走らせることをチームのコーディング規約に組み込むと、レビューコストが激減する。

// src/main.ts の冒頭で実行
import { config } from './config';

if (!config.API_BASE_URL.startsWith('http')) {
throw new Error(`[FATAL] Invalid API_BASE_URL detected at runtime: ${config.API_BASE_URL}`);
}

---

テックリードからの総括

今回紹介した「Runtime Injection」のテクニックは、一見すると泥臭い文字列置換に見えるかもしれない。しかし、「ビルドの不変性を担保しながら、環境ごとの差異を完全にインフラ層へ抽象化する」というコンテナネイティブの思想において、これほど堅牢で確実な手法はない。

CI/CDのパイプライン時間は短縮され、ステージングから本番へのプロモーションは「タグを貼り替えてデプロイするだけ」という真のイミュータブル・インフラストラクチャが実現する。

あなたのプロジェクトでも、無駄な再ビルドの待ち時間からチームを解放し、真に価値のあるコードを書く時間を取り戻してほしい。

タイトルとURLをコピーしました