Next.jsにおけるenvのベストプラクティス
Next.js で env をうまく扱うために僕がよく使う手法を紹介します。
Next.js がサポートしている env の扱い
Next.js はデフォルトで大きく 2 つの方法で env をサポートしています。
- .env ファイルの読み込み
- next.config.js の env キーに記述する
.env ファイルの読み込み
Next.js は .env ファイルを配置することで process.env
に読み込む機能をデフォルトでサポートしています。なのでプロジェクトのルートに、以下のようなファイルを配置してください。
API_ORIGIN=http://localhost:8080
Next.js のプロジェクトでは process.env.DB_HOST
で読み込むことができます。
ref: https://nextjs.org/docs/basic-features/environment-variables
next.config.js の env キーの記述する
Next.js の設定ファイルである next.config.js
には env
というキーが存在します。
module.exports = {
env: {
customKey: 'my-value',
},
}
ref: https://nextjs.org/docs/api-reference/next.config.js/environment-variables
NODE_ENV の存在
環境変数を扱うために、よくあるのは NODE_ENV
に development
や production
などの環境を指す文字列を挿入し、 process.env.NODE_ENV
により実装を分岐させるということをよくやります。
いろんなソースコードでこの使い方はされており、 Next.js もまた NODE_ENV
を扱っている実装があります。
const defaultEnv = command === 'dev' ? 'development' : 'production'
const standardEnv = ['production', 'development', 'test']
if (process.env.NODE_ENV && !standardEnv.includes(process.env.NODE_ENV)) {
log.warn(NON_STANDARD_NODE_ENV)
}
;(process.env as any).NODE_ENV = process.env.NODE_ENV || defaultEnv
上記の実装でもあるように、 Next.js は以下の NODE_ENV
しかサポートしていません。
- production
- development
- test
ただし、現場で Next.js を使っていると「staging 環境では staging.api.example.com が API のオリジンになる」とかがあり、Next.js がサポートしていない NODE_ENV
を使いたい需要はあります。
こう言った場合に使える方法として APP_ENV
などのように別の環境変数を作成する方法です。
APP_ENV を使った Next.js プロジェクトの構成
APP_ENV を使うと NODE_ENV ではサポート出来なかった staging
などの設定をうまく書き分けることができます。そこで使うのは next.config.js の env キーによる環境変数の読み込みです。
next.config.js
を以下のように設定します。
module.exports = {
env: {
...require(`./config/${process.env.APP_ENV || 'local'}.json`),
},
}
プロジェクトのルートに config
というディレクトリを作成し、以下のようにファイルを作成します。
{
"API_ORIGIN": "http://localhost:8080"
}
このプロジェクトを yarn dev
などで起動すると next.config.js
を読み込んだ際に require
をするためその先のファイルを読みに行きます。 NODE_ENV
は development
になり、 APP_ENV
は設定してないので local
になるので config/local.json
を読み込みます。 JSON ファイルを読み込んだらスプレッド演算子により最終的に next.config.js
の env は以下のようになります。
module.exports = {
env: {
API_ORIGIN: "http://localhost:8080",
},
}
この方法であればどんな環境に対してでも対応ができます。
config/staging.json
を作成して以下のようなファイルを作成します。
{
"API_ORIGIN": "https://staging.api.example.com"
}
APP_ENV=staging yarn dev
として起動する、または Docker などの環境に対して APP_ENV=staging
と設定してから yarn start
を行えば、 API_ORIGIN
は https://staging.api.example.com
になっています。
Discussion
この方法シンプルで素敵で採用させていただいています!
.env で定義する方法だと .env.development, .env.production くらいしか分けることができないですが、この方法だと APP_ENV で無制限に環境を切り替えられますし、JSONで定義するからTypeScript型定義の生成も簡単にできますね。
しかし、 .env の方法に物足りなさを感じた読者がこの方法に乗り換えて内部挙動を理解せずに使った時に、上手く動作しなかったりセキュリティ上の問題が発生してしまう恐れがあると感じました。
実際に自分がハマったのですが他の方にも参考になればと思いコメントさせていただきます。
1. 外部ライブラリから参照できない
NextAuth を使っているときに
process.env.NEXTAUTH_URL
を NextAuth が認識してくれなくて気づいたのですが、 next.config.js のenv
に入れた値は Webpack の DefinePlugin を使ってビルド時に置換されるので、Webpack を通さない外部ライブラリからは参照できません。外部ライブラリ(ここでは next-auth)はランタイムで
process.env.NEXTAUTH_URL
を解決しようとするので、 process.env オブジェクトにも値を入れておく必要がありました。2. ブラウザー側にシークレットな値が流出してしまう可能性がある
DefinePlugin で置換されているということは、もしブラウザー側にバンドルされるコードにシークレットな環境変数を参照するコードが混ざっていたら置換されてしまい、流出してしまいます。
例えば JSON に
"SECRET_TOKEN": "my_secret_token"
が定義されているとして、Reactコンポーネント内でprocess.env.SECRET_TOKEN
を参照するとブラウザーにバンドルされる js にmy_secret_token
が混入してしまいます。.env のやり方だと
NEXT_PUBLIC_
のプレフィックスが付いている項目のみブラウザー側から参照できるようになっているのでその辺り安心なのですが、それに安心しているユーザーがこちらに移行した場合、知らずのうちにシークレットな値を公開してしまう可能性があります。.env に倣って
NEXT_PUBLIC_
が付いたもののみブラウザー側に公開しようとするとこうなりました。完全版
ここまで調整した後に「.env ではどうやって
NEXT_PUBLIC_
だけ公開しているんだろう」と思って Next.js のソースをみると、 DefinePlugin に渡す時にprocess.env
からNEXT_PUBLIC
が付いているものだけフィルターしていました。一方で
env
に渡したものはそのまま DefinePlugin に渡されていました。ということは
env
に入れなくてもprocess.env
にだけ入れておけば、Next.js がいい感じにNEXT_PUBLIC_
が付いたものだけブラウザー側でも参照できるようにしてくれるので、最終的にnext.config.js
の中身はこれで良いことになりました。読者の方にも参考になればと思います!
ありがとうございます!!
これ、Next.js 12.3 ではいる .env の編集に対する file-watch してるのでこの方法だと next dev などを一旦やり直さないといけない(HMRできない)というデメリットが生まれましたね🤔
そうですね 😔 今からNext.jsでenvの仕組みを構築する場合は、
.env.{APP_ENV}
を.env.production
にコピーするのが良いかなと思います。