🐭

【Node.js/Next.js】環境変数(.env)のチートシート|2022年1月版

2021/06/07に公開

概要

本記事では、Next.jsにおける環境変数について解説しています。環境変数の基礎だけでなく、本番環境とステージング環境で異なる環境変数を読み込ませたい場合の対処方法も掲載していますので、ぜひ参考にしてもらえればと思います。(需要があるかは諸説)

Next.jsの環境変数周りは少し複雑で公式ドキュメントを読んでも理解しきれないこともあると思います。この記事ではできる限り、初見さんでも理解できるぐらいに噛み砕いて書いていこうと思います。

https://nextjs.org/docs/basic-features/environment-variables

本題の前に

前回の記事が初投稿でしたが、予想より多くのいいねをいただき、驚きを隠せてないです。(本当にありがとうございます🙏)
今後も定期的に投稿できるように頑張っていこうと思っています。しばらくはNext.jsの記事が多いと思いますが、その後はNest.jsFastAPIの記事を書いていければと考えています。

Next.jsにおける環境変数

  • Next.jsの環境変数はNode.jsと同様、process.envに格納される
  • Next.jsをビルドした段階で格納される
  • 格納する値は.env*ファイルに定義できる(後述参照)
  • 通常はクライアントサイドでは読み込めない※1

環境変数の読み込み

バージョン毎の挙動差分

Next.jsの環境変数は周りは、バージョンによって挙動が異なります。旧バージョンを利用しても問題ありませんが、利便性やセキュリティ性が向上するため、筆者的にはアップデート推奨します。

v9.4以上(推奨)

前述しましたが通常、クライアントサイドでは、環境変数を読み込むことができません。と言っても文字だけでは分かりづらいと思いますので、実際のファイルで見てみようと思います。

.env
HOGE=FOO
// サーバーサイド
console.log(process.env.HOGE)
=> FOO

// クライアントサイド
console.log(process.env.HOGE)
=> undefined

特に設定しないと上記のようにクライアントサイドでの読み込み結果がundefinedとなってしまいます。クライアントサイドで環境変数を使用したい場合、.env*で定義する環境変数にプレフィックスを付ける必要があります。

クライアントサイドで使用する場合は変数名にNEXT_PUBLIC_をつける

クライアントサイドで環境変数を使用したい場合、.env*で定義する際に環境変数名をNEXT_PUBLIC_から始まる形式にします。これにより、特に設定せずに環境変数をサーバーサイド/クライアントサイドの両方で読み込むことが可能になります。

.env
HOGE=FOO
NEXT_PUBLIC_HOGE=NEXT_PUBLIC_FOO
// サーバーサイド
console.log(process.env.HOGE)
console.log(process.env.NEXT_PUBLIC_HOGE)
=> FOO
=> NEXT_PUBLIC_FOO

// クライアントサイド
console.log(process.env.HOGE)
console.log(process.env.NEXT_PUBLIC_HOGE)
=> undefined
=> NEXT_PUBLIC_FOO

v9.4以前(非推奨)

v9.4以前の場合、上記の方法は使用することができません。クライアントサイドで使用したい場合は、以下の方法で環境変数を設定ファイルに定義する必要があります。

クライアントサイドで使用する場合はnext.config.jsで設定する

Next.jsの設定ファイルであるnext.config.js内で定義することでクライアントサイドに環境変数を設定できます。この方法はv9.4以上でも利用可能ですが、.env*で定義したものをnext.config.js側にも定義しないといけないため、運用がやや面倒です。個人的には運用しやすいプレフィックスする方法がオススメです。

.env
HOGE=FOO
next.config.js
module.exports = {
  env: {
    HOGE: process.env.HOGE,
  },
}
// サーバーサイド
console.log(process.env.HOGE)
=> FOO

// クライアントサイド
console.log(process.env.HOGE)
=> FOO

https://nextjs.org/docs/api-reference/next.config.js/environment-variables

環境変数ファイルの種類

Next.jsにおける環境変数ファイルは以下の通りです。種類が多いことに加え、読み込まれる環境と優先度があるため、かなり複雑になっています。筆者はどのようにすれば、万人が理解できる構成にできるか頭を悩ませました。後述で筆者が、実際に社内で採用した構成を紹介したいと思います。

ファイル 用途 適応環境 プライオリティ 利用頻度
.env 全ての環境で読み込まれる。環境に依存しない共通の変数を定義する際に使用。 全環境 4
.env.development 開発環境で読み込まれる。開発環境で変数を定義する際に使用。 開発環境(yarn dev実行時) 3
.env.production 本番環境で読み込まれる。本番環境で変数を定義する際に使用。 本番環境(yarn start,yarn build実行時) 3
.env.local 全ての環境で読み込まれる。環境に依存しない共通のAPIキーやDBパスワードなど機密情報を定義する際に使用。 全環境 2
.env.development.local 開発環境で読み込まれる。開発環境におけるAPIキーやDBパスワードなど機密情報を定義する際に使用。 開発環境(yarn dev実行時) 1
.env.production.local 本番環境で読み込まれる。本番環境におけるAPIキーやDBパスワードなど機密情報を定義する際に使用。 本番環境(yarn start,yarn build実行時) 1
package.json
package.json
"scripts": {
  "dev": "next",
  "start:next": "next start",
  "build": "rm -rf .next && next build"
}

読み込まれる環境は全環境開発環境本番環境の3種類あり、プライオリティが高いものから順に読み込まれていきます。少し分かりづらいと思うので、図で可視化させてみます。

nextjs_env_flowchart

ビルドを実行するとプライオリティ順に基づいて、環境変数ファイルを読み込んでいきます。途中、同名の変数があった場合はオーバーライトされます。上記の開発環境側の場合、.envで定義した変数HOGEは、プライオリティの高い.env.development.localで定義した値にオーバーライトされるため、HOGE_ENV_DEVELOPMENT_LOCALとなります。

個人的なベストプラクティス

自身の携わる開発では、上記全て使うと管理ができずに破綻する未来が見えていたので、もっとシンプルな仕組みにしました。環境変数は、基本的にGithubに載せないのが前提(環境変数自体が機密情報であることが多い)という観点から.env.*.localを廃止しました。さらに共通変数という概念が混乱を招くと考え.envも併せて廃止し、以下のような構成になりました。

ファイル 用途
.env.development 開発環境でする環境変数
.env.production 本番環境でする環境変数

非常にシンプルな作りになったと思います。しかし、この構成にしたことで機密情報も同じファイルに定義する形になってしまい、Github上で管理ができない状態になってしまいました。そこで、Cloud Key Management Service(KMS)で暗号化して暗号化したファイルをGithubで管理する方式を採用しました。こちらについては後日、別記事でまとめたいと思います。

nextjs_env_architecture

追記(2021/06/09)

上記のベストプラクティスですが、知り合いのCさんから「開発する際にローカル環境に依存するケースが出てくるから、それにも対応できたほうが良い」と指摘もらったので、その課題を改善する方法を少し考えてみました。
環境で依存するケースに対応する場合に適しているファイルはどれか考えた時、.env.production.local.env.development.localのいずれかが最適と思いました。理由としては、.env.env.localは全ての環境で読み込まれてしまうという仕様上、前述2つと役割が重複してしまい、結果として管理の手間が増えてしまうと考えたからです。

環境毎の設定方法

前述したファイルの種類を見て察した人もいると思いますが、本番環境における環境変数は.env.production.env.production.localの2種類しか存在していません。WEBサービス構築時、本番環境(実際にサーバー上で起動のこと)はプロダクション環境とステージング環境を用意することが多いと思います。するとプロダクション環境とステージング環境で別々の環境変数を定義したい場合、どうするべきかという課題が発生します。

解説する方法の前提

今回は以下の環境での設定方法になります。筆者が基本的にGCPを使用しているため、GCP基準でなってしまっていますが、競合サービスであるAWSのEC2とかでも問題ないと思います。

  • Cloud Run
  • Google App Engine(GAE)

https://cloud.google.com/run
https://cloud.google.com/appengine?hl=ja

シェルスクリプトでファイルを構築する

nextjs_env_script

ビルド前に別に用意した環境変数ファイルから.env.production(または.env.production.local)に内容をコピーする方法です。具体的なコードは以下を参考にしてもらえればと思います。

env.sh
#!/bin/bash
cd $(dirname $0)
APP_ENV=$1
cp .env.$APP_ENV.base .env.production
package.json
"scripts": {
  "dev": "next",
  "start:next": "next start",
  "build": "rm -rf .next && next build",
  "build:env": "./env.sh"
}
.env.production.base
HOGE=FOO
$ yarn build:env production
$ yarn build
$ yarn start

.env.production.baseというベースファイルを用意してスクリプトで.env.productionにコピーする簡単なロジックになります。ビルド前に実行することで、ビルド時に読み込まれる値をベースファイルの中身にできます。ステージング環境の場合は.env.staging.baseとかを用意して、yarn build:env stagingを実行してからビルドすれば問題ありません。

これはあくまで方法の1つで、KMSSecret Managerなどの秘匿情報を管理するサービスを利用する方法もあると思います。別にもっといい方法があれば、ぜひコメントしてもらえると幸いです!

まとめ

  • Next.jsのバージョンで挙動が異なる
  • 特に問題なければv9.4以上にアップデートする
  • クライアントサイドで環境変数を使用する場合は、NEXT_PUBLIC_を付ける
  • Githubにはできる限り、Pushしない
  • .env*には適応される環境と優先度がある
  • 環境毎に分ける場合はシェルスクリプト等で制御が必要

今回は、Next.jsにおける環境変数についてまとめました。できるだけ噛み砕いてまとめたつもりですが、分かりづらい箇所があるかもしれません。(後日、自社のエンジニアに読んでもらってリライトしようと思います)少しでも参考になったと思った方は、いいねしていただけると記事執筆の励みになりますので、よろしくお願いします!

次回は環境変数をCloud Key Management Service(KMS)で暗号化/復号化する方法をまとめる予定です💪

関連記事

https://zenn.dev/kazumax4395/articles/427cc791f6145b

参考記事

https://qiita.com/KZ-taran/items/64cad61096cf45f18c24
https://nextjs.org/docs/basic-features/environment-variables

Discussion