【Nuxt3】Runtime Config と App Config を使い分ける
この記事はユアマイスター アドベントカレンダー 2022の 15 日目の記事です。
Nuxt3 では、アプリケーションの設定値を管理するための機構として Runtime Config と App Config の 2 つが用意されています。
公式ドキュメントではこれらの使い分けについてあまり触れられておらず、実際どう使ったらいいのかでしばらく悩むことになりましたので、 そのとき調べたそれぞれの特徴について書き残しておこうと思います。
TL; DR
以下の方針に従って使い分けておけば、大きな間違いは起こらないと思います。
- シークレット値: Runtime Config
- 環境依存の設定値: Runtime Config
- それ以外: App Config
Runtime Config
Runtime Config はその名の通りアプリケーション実行時(※SSG の場合はビルド時)に値が確定する設定値です。
nuxt.config.ts の runtimeConfig
セクションに記述します。
export default defineNuxtConfig({
runtimeConfig: {
apiKey: 'ABCDEFG123456',
public: {
firebase: {
projectId: 'sample-project',
// ......
},
},
},
})
Runtime Config を利用するには、useRuntimeConfig()
という組み込みの composables を利用します。
const config = useRuntimeConfig()
(App Config と比較してみたときの)Runtime Config の特徴は次の 2 つです。
- 環境変数を用いて値を設定できる
- クライアントに公開されない設定値を保持できる
特徴 1: 環境変数を用いて値を設定できる
Runtime Config はアプリケーションを実行する環境の環境変数から自動的に値を設定する機能を備えています。
たとえば、アプリケーションの実行環境においてNUXT_API_KEY
という環境変数を設定することで、runtimeConfig.apiKey
の値を置き換えることができます。
ネストした値も同様に置き換えることが可能で、上記の例では NUXT_PUBLIC_FIREBASE_PROJECT_ID
という環境変数によってruntimeConfig.public.firebase.projectId
の値を変更することができます。
開発環境と本番環境で異なる設定値を使いたい場合など、アプリケーションの実行環境に応じた設定値を扱う際は Runtime Config が適しています。
環境変数からマップする機能を利用する際は、設定値が文字列(string)として扱われる点に注意が必要です。 環境変数から読み取った値を数値やオブジェクトに変換してくれるような機能はありませんので、nuxt.config.ts に記述するデフォルト値も原則すべて文字列型にする必要があります。
また、Nuxt はnuxi dev
によるアプリケーション実行の際には.env ファイルから環境変数を自動的に展開する機能を備えていますが、ビルド済みのアプリケーションを実行する際は.env ファイルを読み込まないことも覚えておかなければなりません。
特徴 2: クライアントに公開されない設定値を保持できる
Runtime Config の設定は原則としてサーバサイドでのみアクセス可能です。
例外的に、public
という名前空間の下で設定されている値はクライアントサイドでも参照することができるようになります。
ユーザの目に触れてはいけないシークレット値、よくある例でいうと API キーなどは Runtime Config を用いて設定します。
App Config
App Config はアプリケーションをデプロイしたあとに値を書き換える事ができる設定です。 デフォルトの設定値はプロジェクトルートに配置した app.config.ts という名前のファイルに記述します。
export default defineAppConfig({
language: 'ja',
})
App Config を利用するには、useAppConfig()
という組み込みの composables を利用します。
const config = useAppConfig()
(Runtime Config と比較してみたときの)App Config の特徴は次の 2 つです。
- アプリケーション実行中に設定値を書き換えることができる
- リアクティブな設定値である
特徴 1: アプリケーション実行中に設定値を書き換えることができる
App Config はupdateAppConfig()
というユーティリティ関数を使って、アプリケーションのロジックを通じて値を変更することができます。
例えば、サイト全体の言語設定を切り替える composable は次のようになります。
const useLanguageInput = () => {
const config = useAppConfig()
const input = ref(config.language)
// inputの値が変化したらApp Configを更新
watchEffect(() => {
updateAppConfig({ language: input.value })
})
return { input }
}
updateAppConfig()
は app.config.ts に記述されている設定値の型に基づいて引数の型を検査します。
したがって、TypeScript 環境では型安全な設定値の変更が可能となっています。
この composable を利用する Vue コンポーネントは以下のようになります。
<script setup lang="ts">
import { useLanguageInput } from '~/composables'
const { input } = useLanguageInput()
</script>
<template>
<select v-model="input">
<option value="ja">日本語</option>
<option value="en">English</option>
</select>
</template>
特徴 2: リアクティブな設定値である
useAppConfig()
から得られる設定値はリアクティブなオブジェクトになります。
他のリアクティブな値と同様に、データを書き換えることで直ちにコンポーネントの状態を操作することができます。
Runtime Config は必要な値だけを受け取る分割代入が利用できますが、App Config はリアクティブ値であるため無闇に分割代入を行ってはいけません。
結論: ベースは Runtime Config
最初は App Config か Runtime Config のどちらかに設定値を統一できないかと試行錯誤してみたのですが、 サーバサイドレンダリングを行うアプリケーションでは App Config で環境依存の変数をエレガントに扱う方法がありませんでした (もちろん無理やりなんとかしようとすればできなくはないのですが、コードが汚くなってしまうため頑張るほどのメリットは得られないと判断しました)。
妥当性に関しては議論の余地があるとしても、フレームワークを使うときはそのフレームワークの思想に乗っておくほうが将来的な技術的負債の発生を軽減できると考えています。
というわけで、シークレット値の取り扱いの制約を考慮しても Runtime Config を使わざるを得ないのですが、組み込みの.env ファイル読み込み機能が使えないため、 アプリケーションの設定値をどのように管理するかが次の論点となりました。
ユアマイスターでは Nuxt アプリケーションをCloud Runにホストしています。 現時点での結論としては、Cloud Run のサービスを構築する Terraform のテンプレートファイルで管理することとしました。
アプリケーション設定値がアプリケーションのコードベース外で管理されるという点に関してはやや微妙だなとは思いますが、妥協点としてはこんなものかと思います。
App Config は何のためにあるのか
では何のために App Config があるのかに思いを馳せたところ、ロジックから値を変更できるという点を強みとして活かせる機能で使うとよいだろうという結論に至りました。
Runtime Config はアプリケーションをデプロイした時点で設定値が決定してしまうので、設定値を変更しようとおもったら再度デプロイが必要になります。 一方で、App Config はユーザの操作や API などを通じて、デプロイ作業を伴わずに設定を変えることができます。
このような特性が活きる具体的な機能としては、多言語対応、カラーテーマ、あるいはフィーチャートグルなどが挙げられます。 いずれもアプリケーションの実行環境に依存せず、アプリケーションの機能の一部としてユーザや管理者によって変更が可能な設定です。
ストアやuseState
など他にもグローバルな状態を扱う方法は用意されていますが、設定値の管理に関してはこれらに頼るよりも更新の方法が限定されている App Config を使うほうが事故を起こしにくいのではないかと思います。