Vite(React Router)で環境変数が無いよってときの対応
先日、React Routerを使ったプロジェクトにてGitHub Actionsでデプロイしたら、テスト環境でエラーが出ました。格闘の末に「ビルド時」と「実行時」の環境変数が区別できていなかったため、備忘録として残しておきます。
この記事で触れているバージョン関係は次のとおりです。
- Vite v5
- React Router v7
Viteの環境変数の基本
Viteで扱う環境変数は2種類あります。prefixとしてVITE_が付くかどうかの違いです。
VITE_〇〇 | 〇〇 | |
|---|---|---|
| アクセス方法 | import.env.VITE_〇〇 | process.env.〇〇 |
| どこで使われる | クライアント側 | サーバー側 |
| いつ値が決まるか | ビルド時 | 実行時 |
| コードに埋め込まれるか | 埋め込まれる | 埋め込まれない |
基本的にViteで扱う環境変数はVITE_をつけて使います。ビルド時に展開されるため、DBのパスワードのような認証情報は書いてはいけません(むしろこういうのを書かないためにprefixを付けるのです)。
一方、React RouterのSSRでloaderのようなサーバーサイドを扱う場合はprefixを付けず、process.env.〇〇のように書きます。こちらは実行時に参照されます。
公式ドキュメント:環境変数とモード | Vite
Google Maps APIはどっちに書けばいいの?
Google Maps APIのようなAPIは、クライアント側で使うことを想定されているため、VITE_の方で問題ありません。ただしリファラ制限(特定のサイトからのみのアクセスに絞る)を忘れないようにしましょう。
リファラ制限のない普通のAPIキーをVITE_で書いてしまうと、コードに埋め込まれて公開されてしまうのでやめましょう。
どこに環境変数を書くの?
普通にシェルでexportをするのは面倒です。Viteでは.envに書くことで環境変数を設定できます。
VITE_FOO=foo-bar-buz.env.developmentや.env.productionのように環境ごとに用意もできます(環境変数とモード | Vite)。
たとえばGitHub Actionsでデプロイし、ホスティング先をLambda Web Adapterとする場合。
ビルド時に必要なクライアント側の環境変数(VITE_〇〇)は、ワークフロー内で読み込めるようにします(後述)。
実行時に必要なサーバー側の環境変数は、Lambdaの環境変数として設定します。これは手動なりCDKなりで設定します。
ビルド時に埋め込まれるってどういうこと?
「VITE_の環境変数がビルド時に埋め込まれる」とはどういうことなのか、具体的に見ていきましょう。
const foo = import.meta.env.VITE_FOO;環境変数VITE_FOOにfoo-bar-buzという値を書いた場合、次のようにビルド後のコードに値が直接書かれています。
const foo = "foo-bar-buz";string型になる
このimport.meta.env.VITE_〇〇はstring型です。12345のような数値を入れても文字列になるため、numberにしたい場合は自分で変換しましょう。
動的に参照できる
import.meta.env.VITE_〇〇ではなくimport.meta.env[key]のように動的に参照できます。
const value = import.meta.env[key]; // keyがstring型の変数ビルド後のコードを見てみるとimport.meta.envがオブジェクトになっていますね。
const __vite_import_meta_env__ = { "BASE_URL": "/", "DEV": false, "MODE": "production", "PROD": true, "SSR": true, "VITE_MY_NAME": "eetann"};const value = __vite_import_meta_env__[key];環境変数が無かったらエラーにする
上記を踏まえて、環境変数が無ければエラーにする関数を書いてみました。
export function getEnvOrThrow(key: string): string { const value = import.meta.env[key]; if (!value) { throw new Error(`Environment variable ${key} is not set`); } return value;}
export default function Home() { return <>{getEnvOrThrow("VITE_MY_NAME") ?? "名前がないよ"}</>;}エラーが出たらビルド時の環境変数が正しく設定されたか確認しましょう。単に設定し忘れであったりスペルミスであったり、VITE_の付け忘れなどなど。
npx create-react-router@latest env_checkのようにReact Routerのテンプレートを使ってhome.tsxを書き換えれば試せます。
サーバー側の環境変数チェック
React Routerのloaderのようなサーバーサイドの場合は上記の関数のimport.meta.envの部分をprocess.envに書き換えます。
const value = process.env[key];GitHub Actionsでのデプロイ時の注意点
GitHub Actionsでデプロイする場合、次のようにビルド時にenvで渡します。
- name: Build site workspace env: VITE_GOOGLE_MAPS_API_KEY: ${{ secrets.VITE_GOOGLE_MAPS_API_KEY }} VITE_GOOGLE_MAPS_MAP_ID: ${{ secrets.VITE_GOOGLE_MAPS_MAP_ID }} run: npm run build --workspace=@foo-project/site環境変数チェック
筆者は次のように「ビルド時に必要な環境変数をチェックするステップ」を入れています。
- name: Validate required secrets run: | echo "必須secretsの存在確認を実行中..."
# 必須secretsのチェック missing_secrets=()
if [ -z "${{ secrets.VITE_GOOGLE_MAPS_API_KEY }}" ]; then missing_secrets+=("VITE_GOOGLE_MAPS_API_KEY") fi
if [ -z "${{ secrets.VITE_GOOGLE_MAPS_MAP_ID }}" ]; then missing_secrets+=("VITE_GOOGLE_MAPS_MAP_ID") fi
# 不足しているsecretsがある場合はエラー if [ ${#missing_secrets[@]} -gt 0 ]; then echo "❌ 以下の必須secretsが設定されていません:" printf ' - %s\n' "${missing_secrets[@]}" echo "" echo "GitHub Secretsまたはローカルの.secrets*ファイルを確認してください。" exit 1 fi
echo "✅ 全ての必須secretsが設定されています"別ファイルのワークフローを実行するときは要注意!
secretsを使った別ファイルのワークフローを使う場合、そのワークフローを使う側でもsecretsを渡すのを忘れずに。
jobs: build: uses: ./.github/workflows/build-site.yaml secrets: VITE_GOOGLE_MAPS_API_KEY: ${{ secrets.VITE_GOOGLE_MAPS_API_KEY }} VITE_GOOGLE_MAPS_MAP_ID: ${{ secrets.VITE_GOOGLE_MAPS_MAP_ID }}以上、Viteやそのデプロイ関係の環境変数周りの備忘録でした。「ビルド時」の環境変数なのか、「実行時」の環境変数なのか最初は混乱してました。