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やそのデプロイ関係の環境変数周りの備忘録でした。「ビルド時」の環境変数なのか、「実行時」の環境変数なのか最初は混乱してました。