どうもこじらです。
Nuxt3環境で開発をしているとき、「あれ、なんだろうこの微妙な挙動…。開発環境じゃなくて本番環境でも起こる挙動なんだろうか…」と気になることがありました。
具体的に言うと、vue-routerで画面遷移する途中にブラウザバックしようとしたときの挙動です。
普段「npm run dev」で開発モードで起動してるところ、「npm run build」→「npm run start」で起動しようと思ったら、環境ごとに環境変数を切り替える部分がうまく実装できておらず苦戦しました。
結果的にはいい感じに収まったので、解決方法の一例として共有させてもらいます。
dotenvを使わないようにした
Vue2時代、環境ごとに環境変数を読み込ませたい場合は、dotenvか、cross-envのどっちかを使うと良いみたいだなぁ。っていう認識でいました。
しかし、Vue3の時代になるとVue2の時代からはセオリーに変化があるっぽいです。
Vue2時代では定番だったやり方が非推奨になったり、Nuxt公式がどうみても「推奨だよ!」って言ってるのに、その通りに実装してみると「嘘だよ!本当は非推奨!口には出さないけどね!!」って言ってる気がしてならないものがあったり…。
あったりなかったり…。
まぁとりあえず、環境ごとに環境変数を設定したい場合、「こうすると良い!」という理想形は私の中で固まっています。
一旦ゴールとなる理想形について明示したいと思います。
環境ごとに環境変数を設定する際の理想形
環境ごとに環境変数を読み込ませる場合、私が一番良いと思っているルールは以下です。
- 環境変数はそれぞれ1ファイルでまとめる。(以下、環境変数定義ファイルとよぶ。)
- どの環境変数定義ファイルを使用するかは、環境変数を使用して指定する。
- 環境変数は、ソースコードとは切り分けて管理できるようにする。(ソース上に、開発環境の場合、本番環境の場合、みたいな処理を書くのは最高にうんち)
このルールに従って変数を管理した場合、以下のようなメリットがあります。
- セキュリティ的に公開したくない情報を切り分けやすく、非公開にしやすい
- 新しい環境を用意したい場合に融通が利く(開発、本番の2環境で運用していて、追加でstagingを用意したい場合など)
- ソースが読みやすく、管理しやすくなる
経験豊富なエンジニアたちからご意見が飛んできそうですが、まぁ及第点じゃないですかねこれは。
という感じで、今回はNuxt3でこの状態に持っていくのがゴールです。
Nuxt3ではuseRuntimeConfig()を使う
Nuxt2時代は、process.envから環境変数を参照するのがセオリーでしたが、Nuxt3ではuseRuntimeConfig()というAPIを使用します。
nuxt.config.tsに、以下を定義すると…
runtimeConfig: { public: { url: 'http://localhost:8080' } }
Vueのライフサイクル内で以下のように記述することにより、値が取得できます。
useRuntimeCunfig().public.url
ただ、これだけだと環境ごとの値の設定ができないので、これとdotenvを組み合わせることが推奨されています。
詳しい内容は、以下の記事を参照してもらえると良いと思います。
まぁざっくり説明すると、Nuxtのプロジェクトルートに.env.developmentというファイルを作成して配置し、package.jsonのnpm run devのコマンドを以下のように修正します。
nuxt dev --dotenv .env.development
そうすると、.env.developmentの内容をNuxtが取り込んでくれます。
.env.developmentの中身はこんな感じの形式になります。
NUXT_PUBLIC_URL=http://localhost:8080
おぉ!簡単だし整理しやすそうでええやん!!
と思いましたが、世の中そんな甘くありません。
フレームワークというものはエンジニアを甘やかしません。(多方面への皮肉)
よーし、npm run devじゃなくて、npm run buildでも同じようにやってみるかーー
nuxt build --dotenv .env.development
ほいほいっと
はい起動起動
……ん?developmentじゃなくてproduction環境として起動されたんだけど…?なにこれ…?
とりあえず公式を見てみよう…
んーっと。production previewの章かな?
……んん!!!?
やばいちょっと何言ってるか分からない部分が多い…。環境ごとの環境ってどういう括りでの環境だろう…。
でもとりあえず、「npm run preview」で起動することを勧めていることは分かりました。
たしかに、npm run previewで起動してみたら、development環境の環境変数で本番環境として起動することができました。
ただ、npm run previewで起動した場合、本番と同等の挙動になっているかのが分からず、その環境で挙動を確認しても参考材料として良いのか分かりませんでした…。
しかもpreviewだと.env.developmentを固定で読み込みそうな気がするし…。
うーん…。この辺の内容の記事ってまだ少ないし、ちょっとめんどくさくなってきたなぁ…。
dotenvにはこだわりないし別の方法も見てみるかぁ…。
cross-envを試してみる
Vue2時代からお世話になっているcross-envです。これを使うと起動時に環境変数を渡せるようになります。
「npm run dev」で環境変数を渡したい場合、以下のようなコマンドを指定します。
package.json
"scripts": { "dev": "cross-env NODE_ENV=development nuxt dev" }
これが案外便利。
「npm run dev」だけでなく、「npm run build」時も同様に環境変数をNodeに渡すことができます。
Vue2時代は、NODE_ENVの値をdevelopment, staging, productionと切り替えて、環境変数を切り替える実装にしていました。
がしかし!
Vue3時代では、NODE_ENVの使い方に制限がかかっているようです。
なんか、NODE_ENVはdevelopmentとproductionの2つしか指定できない仕様になっているっぽいんですよね。以下の記事を読むと分かります。
「色んなモジュールがNODE_ENVを参照するし、あなたが使うアプリの都合でNODE_ENVを書き換えるのは良くないよー。別の環境変数を自分で定義して、それを使ってくれよー。」って話っぽいです。
試しにcross-envでNODE_ENVの値をstagingに書き換えてサーバを起動しようとしたら以下のような警告が出てきました。
WARN Changing NODE_ENV from staging to development, to avoid unintended behavior.
あかん怒ってるよこれ…。キレ気味の警告だ…。
上記を踏まえ、Nuxt3だと、
- 「npm run dev」→開発専用
- 「npm run preview」→開発環境で本番として動かす用
- 「npm run build & start」→本番環境用
と言う感じで、環境と状況ごとに用意したコマンドがあるから、それを使ってくれって言われてる気がしてなりません。
でもこれだとstaging環境を用意できないし、網羅できてないパターンがいっぱいあるんだよなぁ…。
うーん……。ちょっと話がごちゃごちゃしてきましたね…。
情報は揃ってきたので、一回整理しましょう…。
ここまでの情報の整理
- 環境変数はuseRuntimeConfig()で取得する。つまり、nuxt.config.tsを使用して定義する。
- dotenvは開発モード限定っぽい。(開発モード以外でも使えるのかもしれないが、やり方にたどり着けなかった。)
- cross-envは、Vue2同様に使える。
- NODE_ENVは使わない方が良い。自分で定義した変数を使うべき。
うーん…。dotenv使わなくてよくね?nuxt.config.tsに値を読み込ませられれば良い訳だし…。
環境変数定義ファイルを指定するための環境変数はcross-envで渡せるな…。
やっぱ、環境変数定義ファイルのファイル名はロジックに依存させたくないな……。
…ん!!
ってことはつまり…dynamic importの出番や!!!
実装方法
という感じで、実装方法が確定しました。cross-env + dynamic importで環境変数定義ファイルを読み込ませ、使用する際はuseRuntimeConfig()で使用します。
この実装方法で今のところうまく動いています。
package.json(起動コマンド)
起動コマンドはこんな感じです。APP_ENVという変数名で定義してます。
"scripts": { "dev": "cross-env APP_ENV=development nuxt dev", }
./env/deployment.ts(環境変数定義ファイル)
プロジェクトのルートディレクトリにenvディレクトリを作成し、deployment.tsを配置します。
module.exports = { public: { url: 'http://localhost:8080' }, privateToken: 'hogehoge' }
nuxt.config.tsのruntimeConfigに指定する際の形式で記載してください。それをmodule.exportsで括ります。
nuxt.config.ts
dynamic importは返却値がPromiseの非同期処理なので、async-awaitを付ける必要があります。
その都合上、export defaultとdefineNuxtConfigを分けて書く必要があります。
const defineNuxtConfig = async () => { const envVars: Record<string, string> = await import(`./env/${process.env.APP_ENV}`) return { devtools: { enabled: true }, runtimeConfig: envVars, // 以下省略。nuxt.config.tsでよくあるmodules:[]とかは、ここに書く。 } } export default defineNuxtConfig
懸念点として、defineNuxtConfigのの返却値もPromiseになるので、Nuxt側が上手く読み取ってくれないんじゃないかなーと思ってましたが、なんかうまく読み込んでくれました。
すげぇ…Nuxt.jsはその辺も考慮して実装されてるのか……?
それとも私が知らない定番のルールがあるのか、その辺を意識する必要がない書き方があるのか…。
うーん…。まぁ一旦いいか。
やりたい実装はできたので今回はこんな感じで。なんやかんや実装はシンプルですね。
Nuxt3のナレッジがネット上に溜まってきたら、もっと良いやり方がないか考えようと思います。
こじらでした
じゃ