Nuxt + Docker 環境で runtimeConfig の public env がクライアント遷移後に消える問題とその対処

Docker やコンテナ環境で Nuxt アプリをデプロイすると、初回表示では正常に runtimeConfig.public の値が取得できるのに、クライアントサイドのページ遷移後に値が消える/undefined になる という問題に遭遇することがあります。特に CI/CD パイプラインでビルド → コンテナイメージ作成 → 本番デプロイというフローをとっている場合、環境変数の注入タイミングを誤ると再現しやすく、原因の切り分けに時間を取られがちです。

本記事では、Nuxt + Docker 環境で runtimeConfig.public が消えてしまう原因とその対策 を技術者向けに詳細に解説します。docker-compose での設定例、Nitro 設定、実際の失敗例と解決策も紹介するので、同様の問題で悩んでいる方の参考になるはずです。

1. runtimeConfig.public が消える現象の概要

Nuxt 3 では nuxt.config.ts に定義した runtimeConfig と、.env から読み込んだ環境変数がマージされます。ところが、Docker 環境でアプリをビルド → イメージ化 → コンテナ起動という流れを取ると、イメージ作成時の環境変数が固定化されるため、起動時に設定した環境変数が反映されない ことがあります。

さらに厄介なのが、SSR で初回レンダリングした時点では値が存在しているのに、クライアントサイドのページ遷移後に消える現象 です。これは Nitro サーバーがコンテナ内で実行される際、process.env がビルド時の値を参照しており、クライアント側で hydration 後に値が undefined に置き換わってしまうパターンです。

再現する典型例

// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      API_URL: process.env.API_URL || ''
    }
  }
})

Dockerfile 内で環境変数を固定してしまうと、実行時に差し替えられません。

# ❌ よくある失敗例: ビルド時に環境変数を埋め込んでしまう
ENV API_URL=https://staging.example.com
RUN npm run build
CMD ["node", ".output/server/index.mjs"]

この場合、初回 SSR では API_URL が見えているのに、クライアントサイドナビゲーション後は undefined になる ことがあります。

2. 原因の詳細

この問題の根本原因は、Nuxt の runtimeConfig は Nitro サーバーの起動時に解決されるものであり、ビルド時に固定されると実行時に変更できない という仕様です。Docker イメージに環境変数を焼き込むと、イメージ生成時点の値しか持たなくなり、コンテナ起動時に環境変数を差し替えてもクライアントに反映されない のです。

特に次のような条件が揃うと再現率が高くなります。

  • マルチステージビルドで npm run build を実行してから ENV を設定している
  • docker-compose.ymlenvironment: を設定しているが、イメージがすでにビルド済み
  • CI/CD 環境で .env.production がコンテナビルド時にコピーされている

これにより、サーバーサイドでは古い値を持ち続け、クライアント側では hydration 時に不整合が発生 します。

3. 正しい設定方法

推奨パターン:実行時に環境変数を注入

イメージビルド時には環境変数を埋め込まず、コンテナ起動時に渡す ようにします。

# Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:18-alpine AS runner
WORKDIR /app
COPY --from=builder .output ./.output
# ここでは ENV を書かず、起動時に環境変数を渡す
CMD ["node", ".output/server/index.mjs"]

docker-compose.yml では以下のように起動時に渡します。

version: '3'
services:
  nuxt:
    build: .
    environment:
      API_URL: https://production.example.com
    ports:
      - "3000:3000"

この方法なら、本番環境とステージング環境で同じイメージを使いまわしつつ、起動時に環境変数だけ切り替える ことが可能です。

4. 実際のデバッグ方法

runtimeConfig が消える原因を調査するには、サーバー側とクライアント側の両方で値をログ出力して比較します。

<script setup lang="ts">
const config = useRuntimeConfig()
console.log('Server runtimeConfig:', config.public.API_URL)
onMounted(() => {
  console.log('Client runtimeConfig:', config.public.API_URL)
})
</script>

SSR とクライアント hydration で値が異なれば、環境変数の注入タイミングが問題です。さらに docker exec でコンテナに入って printenv を実行し、環境変数が正しく渡っているか確認すると切り分けがスムーズです。

5. CI/CD・クラウド環境での注意点

クラウド環境(Vercel, Render, ECS など)では、環境変数がデプロイ時に設定されるため、コンテナビルド時に固定してはいけません。特に GitHub Actions や GitLab CI でマルチステージビルドを行う場合、.env.production を COPY するステップを本番用のイメージから除外することで、実行時注入に対応可能なイメージ を作成できます。

また、Kubernetes 環境では ConfigMap や Secret を利用し、Pod 起動時に環境変数を設定することが推奨されます。これにより、再デプロイ時に値を差し替えるだけで環境変更が可能です。

6. まとめ

Nuxt + Docker 環境で runtimeConfig.public が消える問題は、ビルド時に環境変数を固定してしまう設計ミス が原因です。対策としては、以下を守ることが重要です。

  • イメージビルド時に ENV を設定せず、コンテナ起動時に渡す
  • サーバー・クライアント双方で runtimeConfig の値をログ出力して検証
  • CI/CD では .env.production をイメージに含めないように注意
  • 本番環境では環境変数マネージャー(K8s ConfigMap, ECS task env など)を使う

これらを守ることで、環境差異による runtimeConfig 消失を防ぎ、安定したデプロイが可能になります。

参考

タイトルとURLをコピーしました