Viteで本番だけログインできない。VITE_の付け忘れとimport.meta.envの誤解だった

Vercelにデプロイしたら、ビルドは成功したのに本番環境でログインボタンを押しても一切反応しなかった現象がありました。

コンソールを開いてみると次のようなエラーが出ていました。

TypeError: Cannot read properties of undefined (reading 'split')
at parseAuthToken (auth.ts:42:18)
at handleLoginClick (LoginButton.tsx:28:9)

undefinedsplitメソッドを呼ぼうとしてる。つまり認証トークンがundefinedのまま関数に渡されてる。ネットワークタブを見たら、リクエスト先が https://undefined/api/v1/auth になってました。

環境変数がうまく読み込まれてないのかなと思って、Vercelのダッシュボードを開いたら、API_URLACCESS_KEYもちゃんと登録されていて、Production環境のチェックも入ってる状態でした。

キャッシュや反映遅延じゃなかった

最初はVercel側のキャッシュか反映が遅れてるのかなと思い、シークレットウィンドウで開き直したり、再デプロイしてみたけど改善しなかった。「vite 本番 環境変数 undefined」あたりで検索しながら原因を探りました。

vite.config.tsのdefineでprocess.envを注入

調べるとimport.meta.envを使うのが必要らしかったけど、自分は環境変数をちゃんと参照させてるつもりでした。それでvite.config.tsdefineprocess.envを注入するのも試してみたんですが、ソース側でimport.meta.envを参照してるかぎり影響なくて、うまくいきませんでした。

環境変数にVITE_がついてなかった

自分のコードと.envファイルで環境変数を参照してる部分をGrepしてみて、見落としてた原因がわかりました。

まず.envの変数名にVITE_プレフィックスがついてなかったです。

API_URL=https://api.example.com

実はViteの公式ドキュメントにちゃんと書いてありました。

To prevent accidentally exposing env variables to the client, only variables prefixed with VITE_ are exposed to your Vite-processed code.

セキュリティ上、VITE_がついてない変数はクライアント側のコードに絶対露出しないのです。なのでビルド時にコードの中に値が埋め込まれません。

それでソースコード側でも、import.meta.env.VITE_API_URLみたいにVITE_プレフィックスを含めて参照すべきなのに、自分はimport.meta.env.API_URLみたいにプレフィックス抜きで参照してました。ViteではVITE_がついていない変数はimport.meta.envから外されるから、ビルド後のコードで存在してないプロパティにアクセスすることになって、実行時にundefinedとなってしまいます。

Vercelのダッシュボードに環境変数を設定してても、Viteのビルド時にコードに静的に埋め込まれないと意味がないです。なのでAPI URLやトークンが取得できずhttps://undefined/...になってました。原因が分かったところでコードを修正します。

VITE_プレフィックスをつけてimport.meta.envに統一した

修正自体は単純で、.env.env.productionの変数名を全部直して、

VITE_API_URL=https://api.example.com
VITE_ACCESS_KEY=12345
VITE_APP_MODE=production

ソースコード側もすべてimport.meta.env.VITE_API_URLに統一。試行錯誤で触ってたvite.config.tsは元に戻しました。コミットメッセージは「fix: 環境変数のプレフィックスを修正しimport.meta.envに統一」。

修正を反映して再デプロイしたら、無事にログインできてダッシュボード画面に移動しました。

Viteの環境変数はビルド時に埋め込まれる

SPAの環境変数は実行時じゃなくてビルド時にコード内の文字列として埋め込まれる。あと、ViteではVITE_プレフィックスが必須。環境変数の仕様についてはちゃんと確認しておかねばって思いました。