1. 結論(この記事で得られること)
Nuxt3のruntimeConfigと環境変数(.env)の違い、正直よく分からないまま使っていませんか?私も最初はそうでした。
この記事で得られる実務知識:
- runtimeConfigとenvの根本的な違いと、使い分けの明確な判断基準
- 「クライアント側に漏れてはいけない値」を守る安全な設計方法
- ビルド時 vs ランタイム時の挙動差異とパフォーマンス影響
- AIを活用した環境変数の問題切り分け最速フロー
- 本番環境で死活問題になる失敗パターン(実例付き)
レビューで「これクライアントに秘密鍵漏れますよ」と指摘される前に、この記事で正しい知識を身につけましょう。
2. 前提(環境・読者層)
想定環境:
- Nuxt3(3.8以降推奨)
- Node.js 18以降
- SSR / SSGどちらでも対応
想定読者:
- Nuxt3でAPI連携を始めたが、環境変数の扱いに不安がある方
- 「import.meta.env」と「useRuntimeConfig()」の違いが曖昧な方
- セキュリティレビューで秘密情報の扱いを指摘された方
3. Before:よくあるつまずきポイント
実務でよく見る危険なパターンを3つ挙げます。昔の私もやらかしました。
3-1. 秘密鍵をpublicに置いてしまう
// ❌ 危険:これは絶対ダメ
export default defineNuxtConfig({
runtimeConfig: {
public: {
apiSecret: process.env.API_SECRET // クライアントに漏れる!
}
}
})
このコードをビルドすると、API_SECRETがブラウザのJavaScriptバンドルに含まれます。開発者ツールで誰でも見られる状態です。
3-2. サーバー側の値をクライアントで参照しようとする
<script setup>
// ❌ これはクライアントでundefinedになる
const config = useRuntimeConfig()
const secret = config.apiSecret // public以外はクライアントで取れない
</script>
「ローカルでは動いたのに本番で動かない」の典型パターン。SSRとクライアントハイドレーションの境界を理解していないと起きます。
3-3. import.meta.envとruntimeConfigの混在
// ❌ 混乱の元
const apiUrl = import.meta.env.VITE_API_URL // Vite由来
const config = useRuntimeConfig() // Nuxt由来
両方使えるが故に、チームで統一されず、後からメンテナンスできなくなります。
4. After:基本的な解決パターン
4-1. 正しい使い分けの判断基準
シンプルな原則:
秘密(DB接続情報、外部API秘密鍵)
使う場所: 「runtimeConfig.xxx」
アクセス方法: サーバーのみ
公開OK(APIエンドポイント、GA ID)
使う場所: 「runtimeConfig.public.xxx」
アクセス方法: サーバー・クライアント両方
ビルド時に固定したい値
使う場所: 「.env」 + 「import.meta.env」
アクセス方法: Viteビルド時
4-2. 安全な設定例
nuxt.config.ts:
export default defineNuxtConfig({
runtimeConfig: {
// サーバー専用(秘密情報)
dbUrl: process.env.DATABASE_URL,
apiSecret: process.env.API_SECRET_KEY,
// publicは省略可能な公開情報
public: {
apiBase: process.env.NUXT_PUBLIC_API_BASE || 'https://api.example.com',
gtmId: process.env.NUXT_PUBLIC_GTM_ID
}
}
})
.env:
# サーバー専用(runtimeConfigで受け取る)
DATABASE_URL=postgresql://user:pass@localhost:5432/db
API_SECRET_KEY=super_secret_key_12345
# クライアントでも使う(NUXT_PUBLIC_プレフィックス)
NUXT_PUBLIC_API_BASE=https://api.example.com
NUXT_PUBLIC_GTM_ID=GTM-XXXXXX
重要な命名規則:
- 「NUXT_PUBLIC_」プレフィックスは自動的に「runtimeConfig.public」にマッピングされる
- プレフィックスなしは「runtimeConfig」のルートに入る
4-3. 使用例
サーバーサイド(server/api/users.ts):
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
// ✅ サーバーでは秘密情報にアクセスできる
const db = await connectDB(config.dbUrl)
const users = await db.query('SELECT * FROM users')
return users
})
クライアントサイド(pages/index.vue):
<script setup>
const config = useRuntimeConfig()
// ✅ クライアントではpublicのみアクセス可能
const { data } = await useFetch(`${config.public.apiBase}/users`)
// ❌ これはundefined(クライアントでは見えない)
console.log(config.dbUrl) // undefined
</script>