⚡️Next.jsにおけるenvのベストプラクティス2021/11/03に公開3件Next.js環境変数techGitHubで編集を提案Discussionaku2022/04/06に更新この方法シンプルで素敵で採用させていただいています! .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 オブジェクトにも値を入れておく必要がありました。 const env = require(`./config/${process.env.APP_ENV || 'local'}.json`); Object.entries(env).forEach(([key, value]) => { process.env[key] = value; }); module.exports = { env, } 2. ブラウザー側にシークレットな値が流出してしまう可能性がある DefinePlugin で置換されているということは、もしブラウザー側にバンドルされるコードにシークレットな環境変数を参照するコードが混ざっていたら置換されてしまい、流出してしまいます。 例えば JSON に "SECRET_TOKEN": "my_secret_token" が定義されているとして、Reactコンポーネント内で process.env.SECRET_TOKEN を参照するとブラウザーにバンドルされる js に my_secret_token が混入してしまいます。 .env のやり方だと NEXT_PUBLIC_ のプレフィックスが付いている項目のみブラウザー側から参照できるようになっているのでその辺り安心なのですが、それに安心しているユーザーがこちらに移行した場合、知らずのうちにシークレットな値を公開してしまう可能性があります。 .env に倣って NEXT_PUBLIC_ が付いたもののみブラウザー側に公開しようとするとこうなりました。 const env = require(`./config/${process.env.APP_ENV || 'local'}.json`); const publicEnv = {} Object.entries(env).forEach(([key, value]) => { if(key.startsWith("NEXT_PUBLIC_")) { publicEnv[key] = value; } process.env[key] = value; }); module.exports = { env: publicEnv, } 完全版 ここまで調整した後に「.env ではどうやって NEXT_PUBLIC_ だけ公開しているんだろう」と思って Next.js のソースをみると、 DefinePlugin に渡す時に process.env から NEXT_PUBLIC が付いているものだけフィルターしていました。 https://github.com/vercel/next.js/blob/384953b35c5e9935bb4a2fcdfe5056efb73cd740/packages/next/build/webpack-config.ts#L1298-L1307 一方で env に渡したものはそのまま DefinePlugin に渡されていました。 https://github.com/vercel/next.js/blob/384953b35c5e9935bb4a2fcdfe5056efb73cd740/packages/next/build/webpack-config.ts#L1307-L1313 ということは env に入れなくても process.env にだけ入れておけば、Next.js がいい感じに NEXT_PUBLIC_ が付いたものだけブラウザー側でも参照できるようにしてくれるので、最終的に next.config.js の中身はこれで良いことになりました。 const env = require(`./config/${process.env.APP_ENV || 'local'}.json`); Object.entries(env).forEach(([key, value]) => { process.env[key] = value; }); module.exports = {} 読者の方にも参考になればと思います! 返信を追加JJ2023/08/19に更新ありがとうございます!! これ、Next.js 12.3 ではいる .env の編集に対する file-watch してるのでこの方法だと next dev などを一旦やり直さないといけない(HMRできない)というデメリットが生まれましたね🤔 aku2023/11/27 これ、Next.js 12.3 ではいる .env の編集に対する file-watch してるのでこの方法だと next dev などを一旦やり直さないといけない(HMRできない)というデメリットが生まれましたね🤔 そうですね 😔 今からNext.jsでenvの仕組みを構築する場合は、 ローカル開発時はNext.jsのenvの仕組みを使ってホットリロードの恩恵を受ける ビルド・デプロイ時はDockerを使った公式のExampleにあるように、ビルド前に .env.{APP_ENV} を .env.production にコピーする のが良いかなと思います。 https://github.com/vercel/next.js/blob/6bfd1458b2913239779f810d93ee13bb14e98076/examples/with-docker-multi-env/docker/staging/Dockerfile#L25-L27 返信を追加
aku2022/04/06に更新この方法シンプルで素敵で採用させていただいています! .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 オブジェクトにも値を入れておく必要がありました。 const env = require(`./config/${process.env.APP_ENV || 'local'}.json`); Object.entries(env).forEach(([key, value]) => { process.env[key] = value; }); module.exports = { env, } 2. ブラウザー側にシークレットな値が流出してしまう可能性がある DefinePlugin で置換されているということは、もしブラウザー側にバンドルされるコードにシークレットな環境変数を参照するコードが混ざっていたら置換されてしまい、流出してしまいます。 例えば JSON に "SECRET_TOKEN": "my_secret_token" が定義されているとして、Reactコンポーネント内で process.env.SECRET_TOKEN を参照するとブラウザーにバンドルされる js に my_secret_token が混入してしまいます。 .env のやり方だと NEXT_PUBLIC_ のプレフィックスが付いている項目のみブラウザー側から参照できるようになっているのでその辺り安心なのですが、それに安心しているユーザーがこちらに移行した場合、知らずのうちにシークレットな値を公開してしまう可能性があります。 .env に倣って NEXT_PUBLIC_ が付いたもののみブラウザー側に公開しようとするとこうなりました。 const env = require(`./config/${process.env.APP_ENV || 'local'}.json`); const publicEnv = {} Object.entries(env).forEach(([key, value]) => { if(key.startsWith("NEXT_PUBLIC_")) { publicEnv[key] = value; } process.env[key] = value; }); module.exports = { env: publicEnv, } 完全版 ここまで調整した後に「.env ではどうやって NEXT_PUBLIC_ だけ公開しているんだろう」と思って Next.js のソースをみると、 DefinePlugin に渡す時に process.env から NEXT_PUBLIC が付いているものだけフィルターしていました。 https://github.com/vercel/next.js/blob/384953b35c5e9935bb4a2fcdfe5056efb73cd740/packages/next/build/webpack-config.ts#L1298-L1307 一方で env に渡したものはそのまま DefinePlugin に渡されていました。 https://github.com/vercel/next.js/blob/384953b35c5e9935bb4a2fcdfe5056efb73cd740/packages/next/build/webpack-config.ts#L1307-L1313 ということは env に入れなくても process.env にだけ入れておけば、Next.js がいい感じに NEXT_PUBLIC_ が付いたものだけブラウザー側でも参照できるようにしてくれるので、最終的に next.config.js の中身はこれで良いことになりました。 const env = require(`./config/${process.env.APP_ENV || 'local'}.json`); Object.entries(env).forEach(([key, value]) => { process.env[key] = value; }); module.exports = {} 読者の方にも参考になればと思います! 返信を追加
JJ2023/08/19に更新ありがとうございます!! これ、Next.js 12.3 ではいる .env の編集に対する file-watch してるのでこの方法だと next dev などを一旦やり直さないといけない(HMRできない)というデメリットが生まれましたね🤔 aku2023/11/27 これ、Next.js 12.3 ではいる .env の編集に対する file-watch してるのでこの方法だと next dev などを一旦やり直さないといけない(HMRできない)というデメリットが生まれましたね🤔 そうですね 😔 今からNext.jsでenvの仕組みを構築する場合は、 ローカル開発時はNext.jsのenvの仕組みを使ってホットリロードの恩恵を受ける ビルド・デプロイ時はDockerを使った公式のExampleにあるように、ビルド前に .env.{APP_ENV} を .env.production にコピーする のが良いかなと思います。 https://github.com/vercel/next.js/blob/6bfd1458b2913239779f810d93ee13bb14e98076/examples/with-docker-multi-env/docker/staging/Dockerfile#L25-L27 返信を追加
aku2023/11/27 これ、Next.js 12.3 ではいる .env の編集に対する file-watch してるのでこの方法だと next dev などを一旦やり直さないといけない(HMRできない)というデメリットが生まれましたね🤔 そうですね 😔 今からNext.jsでenvの仕組みを構築する場合は、 ローカル開発時はNext.jsのenvの仕組みを使ってホットリロードの恩恵を受ける ビルド・デプロイ時はDockerを使った公式のExampleにあるように、ビルド前に .env.{APP_ENV} を .env.production にコピーする のが良いかなと思います。 https://github.com/vercel/next.js/blob/6bfd1458b2913239779f810d93ee13bb14e98076/examples/with-docker-multi-env/docker/staging/Dockerfile#L25-L27
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にコピーするのが良いかなと思います。