🏔️

Next.js App Router 実行時環境変数の参照方法を考えたよ。

2025/01/25に公開

本記事では、Next.js アプリケーションにおいて「ビルド時」ではなく「実行時」に環境変数を参照する方法について解説します。特に、Client Component から環境変数を扱いたい場合に焦点を当てます。
例えば、デプロイ先や環境(production、stagingなど)に合わせて設定を切り替える必要がある場合など、ビルド済みのコードに依存しない形で環境変数を読み込むニーズは少なくありません。

さっそくですが、基本的な実装例は以下のようになると思います。

  1. Server Component で環境変数を取得:
    Server Component にて process.env を介して実行時環境変数を取得する。
  2. ContextProvider に環境変数を渡す:
    取得した実行時環境変数を ContextProvidervalue に指定する。

そして、このように Client Component から Context の値を参照できるかと思います。

ClientComponent.tsx
export const ClientComponent = () => {
  const env = useEnvContext(); // Contextを利用するカスタムフック 

  return (
    <div>
      <h1>環境変数の確認!</h1>
      <pre>{JSON.stringify(env, null, 2)}</pre>
    ...

しかし、hooks(useEnvContext)が利用できない場面ではどう対応するべきか?
例えば、API リクエストを行う関数内で、API の baseURL を実行時環境変数から参照したいケースを考えてみます。(引数で渡して解決する方法もありますが、ここでは考えないことにします)

つまり、hooks(useEnvContext)が利用できないケースに対応する必要があります。
今回は window オブジェクトから参照できるような機構を作り対応してみましょう。
実装例は以下になります。

layout.tsx
export default function Layout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const env = getEnv();
  return (
    <html lang="ja">
      <head>
        <Script
          id="runtimeEnv"
          strategy="beforeInteractive"
        >{`window.RUNTIME_ENV = ${JSON.stringify(env)}`}</Script>
      </head>
      <body>
        {children}
      </body>
    </html>
  );
}

Script タグ内で window オブジェクトに、パースした環境変数を格納しています。
また、一番最初に読み込む必要のある重要なスクリプトなので、beforeInteractive 属性を指定しております。
beforeInteractive に関する説明は以下を参照してください。
https://nextjs.org/docs/app/api-reference/components/script#beforeinteractive
注意点としては、検証ツールから閲覧可能な状態になってしまいますので、公開しても問題ない環境変数のみを格納しましょう。

getEnv の実装例はこちらです。
ここで window オブジェクトに格納したい環境変数を抽出しています。
server/client の両環境から同じ命名でアクセスできるようにする必要があるため注意しましょう。
(例えば、API_URL: process.env.BASE_URL など書かないようにしましょう)
T3 Env を利用すると強力な型付が可能になりそうですね。

libs/env.ts
export const getEnv = () => {
  noStore(); // unstable_noStore
  return {
    BASE_URL: process.env.BASE_URL,
  };
};

そして、実行時環境変数を参照するための関数を作成します。
サーバ環境からは process.env から、クライアント環境からは window オブジェクトから値を参照することができます。

libs/env.ts
export const getRuntimeEnv = (
  key: keyof ReturnType<typeof getEnv>
): string | undefined => {
  const isBrowser = Boolean(typeof window !== "undefined");
  if (isBrowser) {
    return window["RUNTIME_ENV"]?.[key];
  }

  noStore(); // unstable_noStore
  return process.env?.[key];
};

利用例はこちらです。
この api を利用して、routeHandler もしくは ClientComponent から API通信処理を実装することができます。

libs/api.ts
export const api = axios.create({
  baseURL: getRuntimeEnv("BASE_URL"),
  headers: {
    "Content-Type": "application/json",
  },
  ...
});

以上。
本記事では、hooks が利用できない環境において実行時環境変数を参照する方法例を紹介しました。
ご意見や改善点がありましたら、ぜひコメントなどでご指摘ください。

Discussion