◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/開発ツール
⬡ 開発ツール/2026-10-15初級

EXPO_PUBLIC_ の値が、開発では読めて本番だけ undefined になる書き方を、変換結果で見分けます

API の URL が開発中は読めるのに、ビルドした途端に undefined になる。原因は process.env の書き方でした。babel-preset-expo の変換結果を開発・本番で並べ、置き換わる書き方と残ってしまう書き方を表にしました。

Expo218環境変数10EXPO_PUBLIC3Rork582トラブルシューティング85

開発中は問題なく動いていた画面が、ビルドを作って実機に入れた途端に、データを取りにいかなくなることがあります。通信エラーの表示も出ません。ただ空のままです。

こういうとき、私が最初に疑うのは API の URL が空になっていないか、という点です。個人開発のアプリでも、サーバーの向き先を環境変数で切り替える構成はよく使います。向き先が undefined のまま本番に出ると、症状だけが静かに消えたように見えてしまいます。

先にお伝えしたいのは、開発サーバーで読めたことは、本番で読めることの証拠にならないということです。理由を、変換の結果を並べて確かめました。

何をどこまで確かめたか

確かめたのは Expo が使っている Babel の変換と、開発サーバーが差し込む 1 行です。バージョンは babel-preset-expo 57.0.15、@expo/metro-config 57.0.13、Node 22 です。

Metro で実際にバンドルし、Hermes で動かしたわけではありません。ソースを変換し、その結果を process.env に NODE_ENV だけが入った状態で実行する、という手元の再現です。実機で同じになるかは、最後の手順で各自の環境で確かめてください。

ここで見えたのは、開発と本番とで 環境変数の渡り方がまったく別物 だということです。

  • 本番ビルド: process.env.EXPO_PUBLIC_名前 と書かれた場所が、ビルド時に文字列そのものへ置き換わります。置き換わらなかった場所には何も残りません。
  • 開発サーバー: 置き換えとは別に、EXPO_PUBLIC_ で始まる変数をすべて process.env に差し込む 1 行が、バンドルの先頭に入ります。

つまり開発中は、置き換えが起きなくても process.env の中身を直接読めてしまうのです。

書き方ごとの結果

同じ値 https://api.example.com を渡して、書き方を変えながら開発と本番で実行しました。

書き方 開発 本番
process.env.EXPO_PUBLIC_API_URL読める読める
const { EXPO_PUBLIC_API_URL } = process.env読める読める
process.env["EXPO_PUBLIC_API_URL"]読める読める
process.env[key](key は変数)読めるundefined
const env = process.env; env.EXPO_PUBLIC_API_URL読めるundefined
read(process.env)(関数へ丸ごと渡す)読めるundefined
process.env.API_URL(接頭辞なし)undefinedundefined

表の太字の 3 行が、開発では動くのに本番だけ壊れる書き方です。共通点は、名前が 1 つの式の中で完結していないことでした。置き換えは process.env と名前が同じ式に並んでいるときだけ働きます。変数を経由すると、どの名前を読むのかが書いた時点で決まらず、置き換えの対象から外れます。開発で動いたことを根拠に安心してしまうのは、無理もないことだと感じています。

もう一つ、手元で意外だったことがあります。公式のドキュメントには、分割代入や角括弧の書き方は置き換えの対象にならないと読めます。ところが今回の版では、分割代入と文字列リテラルの角括弧は置き換わりました。ドキュメントの記述が古いのか、設定や版によって変わるのかまでは、私には切り分けられていません。

「通る書き方」は版で動きうるので、頼らずに直接の形へ寄せておきます。 この一文を、いまの私の線引きにしています。

自分のコードから危ない行を探す

探す対象は、process.env のあとに .名前 が続いていない行です。

# process.env の直後が「.」でない行だけを出します
grep -rnE "process\.env(\[|[^.A-Za-z_\[]|$)" src \
  --include="*.ts" --include="*.tsx" --include="*.js" --include="*.jsx"

試しに 7 行ほどの検証用ファイルに当てたところ、変数キー・別名・関数への受け渡しの 3 行だけが出ました。文字列リテラルの角括弧は安全なのに引っかかりますので、出てきた行を一つずつ見る前提の道具です。

直接の形に集めて、空なら起動時に止める

直す方針は、読む場所を 1 か所に集め、そこだけ直接の形で書くことです。空のときは、画面が空になる前に止めます。

// src/env.ts
// 値は「process.env.名前」と直接書いて拾います。ここがビルド時に文字列へ置き換わります。
const raw = {
  EXPO_PUBLIC_API_URL: process.env.EXPO_PUBLIC_API_URL,
  EXPO_PUBLIC_SENTRY_DSN: process.env.EXPO_PUBLIC_SENTRY_DSN,
} as const;
 
const missing = Object.entries(raw)
  .filter(([, value]) => !value)
  .map(([name]) => name);
 
if (missing.length > 0) {
  throw new Error(`公開環境変数が空です: ${missing.join(', ')}`);
}
 
export const env = raw as Record<keyof typeof raw, string>;

他のファイルは import { env } from "./env" として、env.EXPO_PUBLIC_API_URL を使います。

この形を本番設定で変換して実行すると、2 つとも値があれば通り、片方を消すと 公開環境変数が空です: EXPO_PUBLIC_SENTRY_DSN で止まりました。止まる場所が起動直後になるので、空の画面を前に原因を探す時間が減ります。ここで止めるのは、本番ビルドに値が入らなかったことを、利用者より先に自分が知るためです。

実機で確かめる最後の一手

手元の再現は変換までですので、実際のバンドルでも見ておくと安心です。ビルドの成果物は、値が文字列として入っているかを検索できます。

# 本番設定でバンドルを書き出し、API の URL が含まれているかを数えます
npx expo export --platform ios
grep -rl "https://api.example.com" dist | head

ここでは api.example.com を、ご自身の向き先に置き換えてください。1 件も出なければ、置き換えが起きていません。なお、この方法は値がバンドルに入る前提の確認です。入ってはいけない秘密の値は、そもそも EXPO_PUBLIC_ に載せない、という整理は EAS の secret は「アプリに入れない」設定ではありません に書き残しております。

まずは今日、手元のプロジェクトで上の grep を一度だけ実行して、出てきた行の数を確かめてみてください。0 行であれば、このまま進めて大丈夫です。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

⬡ 開発ツール2026-10-13
リリース前に、書き出した Expo バンドルの中から自分の鍵を探してみる
ソースに鍵がなくても、出来上がったアプリの中に鍵が残ることがあります。Expo SDK 57 で実際に expo export を実行し、バンドルの中身を grep と 60 行ほどの Node スクリプトで調べて、見つかる鍵と見つからない鍵の違いを確かめました。
⬡ 開発ツール2026-10-07
日本語の見出しだけ太字にならない — Android で fontWeight が効かないときの切り分けと直し方
Rork で作ったアプリで、iPhone では太字なのに Android では日本語の見出しが細いままになる症状の切り分け記事です。原因の見分け方、fontFamily を重みごとに分ける直し方、確認用コードを載せています。
⬡ 開発ツール2026-09-21
exit code 0 で止まるビルド — ログを黙らせていた設定と、空ファイルを通した存在チェック
eas build --local が exit code 0 but produced no further output で止まる件と、0 バイトのスタブが埋め込まれず dyld で落ちる件。どちらも原因は出力を減らす設定でした。自分のスクリプトを棚卸しする手順まで書き残します。