Nuxtの runtimeConfig とは、APIキーや接続先URLなどの設定を「ビルドした後でも環境変数で差し替えられる」形で持つための仕組みです。nuxt.config.ts にキーを定義しておき、実行時に NUXT_ で始まる環境変数を渡すと値が上書きされます。

ただし「環境変数を設定したのに反映されない」というつまずきがとても多い機能でもあります。この記事では Nuxt 4.5.2 で実際にビルドし、node .output/server/index.mjs に環境変数を渡して上書きされる・されないを1つずつ実測しました。キー名の変換規則、.env が本番で読まれない件、public に置いた値がHTMLに出る件などを、実行結果つきで確認できます。

検証環境:Node.js 22.22.2 / Nuxt 4.5.2 / Nitro 2.13.4 / Vue 3.5.43 / Playwright 1.56.1

runtimeConfigと環境変数の早見表

置き場所 読める場所 実行時に環境変数で上書き ブラウザに届くか 向いている値
runtimeConfig.xxx(トップレベル) サーバーのみ できる(NUXT_XXX) 届かない APIキー、DBパスワード、外部APIの秘密
runtimeConfig.public.xxx サーバー+クライアント できる(NUXT_PUBLIC_XXX) HTMLに埋め込まれる 公開してよいAPIのURL、サイト名
app.config.ts サーバー+クライアント できない(ビルド時に決まる) 届く テーマ色、機能のON/OFFなど見た目の設定
nuxt.config.ts で process.env.XXX を読む ビルド時のみ できない(ビルド時の値で固定) 置き場所による ビルドの切り替え
.env ファイル nuxt dev / build / preview ― ― 開発用。本番の node .output/... では読まれない

環境変数名の変換規則(すべて実測で確認):

nuxt.config のキー 上書きする環境変数 結果
apiSecret NUXT_API_SECRET 上書きされた
db.host(ネスト) NUXT_DB_HOST 上書きされた
db.port(数値) NUXT_DB_PORT=6543 数値 6543 になった
public.apiBase NUXT_PUBLIC_API_BASE 上書きされた
session.cookie.secure NUXT_SESSION_COOKIE_SECURE=false false になった
apiSecret NUXT_APISECRET / NUXT_apiSecret / API_SECRET 反映されない
db.host NUXT_DB__HOST(アンダースコア2つ) 反映されない
定義していない notDefinedKey NUXT_NOT_DEFINED_KEY 反映されない(キー自体が増えない)

規則は「NUXT_ + キーを大文字スネークケース(camelCaseの区切りとネストの区切りをどちらも _ 1つ)」です。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Nuxt runtimeConfigと環境変数|NUXT_が効かない原因を実測」

基本の書き方(コピペ用)

// nuxt.config.ts
export default defineNuxtConfig({
  compatibilityDate: '2026-09-01',
  runtimeConfig: {
    // ここから下はサーバーだけで読める(NUXT_API_SECRET で上書き)
    apiSecret: '',
    // ネスト:NUXT_DB_HOST / NUXT_DB_PORT で上書き
    db: {
      host: 'localhost',
      port: 5432,
    },
    // public の中はクライアントにも届く(NUXT_PUBLIC_API_BASE で上書き)
    public: {
      apiBase: '/api',
      siteName: '備品管理デモ',
    },
  },
})

ポイントは「既定値(空文字でもよい)を必ず書いておくこと」です。 キーが定義されていないと、同名の環境変数を渡しても無視されます(後述)。

サーバー側(APIルート)で読む例です。

// server/api/config-check.get.ts(検証用:本番で秘密を返すAPIは作らないこと)
export default defineEventHandler((event) => {
  const config = useRuntimeConfig(event)
  return {
    apiSecret: config.apiSecret ? `${config.apiSecret.slice(0, 4)}…(${config.apiSecret.length}文字)` : '(空)',
    db: config.db,
    dbPortType: typeof config.db.port,
    public: config.public,
  }
})

画面(Vueコンポーネント)で読む例です。

<!-- app/pages/config.vue -->
<script setup lang="ts">
const config = useRuntimeConfig()
const appConfig = useAppConfig()
</script>

<template>
  <main>
    <h1>{{ config.public.siteName }}</h1>
    <p>apiBase: {{ config.public.apiBase }}</p>
    <p>テーマ色: {{ appConfig.theme.primaryColor }}</p>
    <ClientOnly>
      <p>クライアントから apiSecret: {{ String(config.apiSecret) }}</p>
    </ClientOnly>
  </main>
</template>

ブラウザで開いた結果(Playwrightで取得):

クライアントから apiSecret: undefined

トップレベルのキーはブラウザでは undefined になります。 画面のコードで秘密の値を使う必要がある処理は、サーバーAPIに移します。

useRuntimeConfigの使いどころ

書く場所 書き方 読める範囲
server/api/**・server/routes/** useRuntimeConfig(event) 全部(秘密も)
server/middleware/**・server/plugins/** useRuntimeConfig(event)(イベントがない所は引数なし) 全部
app/pages・app/components・composable useRuntimeConfig() サーバー描画中もブラウザでも public だけを使う前提で書く
app/plugins/**・ルートミドルウェア useRuntimeConfig() 同上
nuxt.config.ts の中 使えない(process.env はビルド時の値) ―

サーバーのルートでは useRuntimeConfig(event) と event を渡すのが公式の推奨です。 環境変数による上書きを、リクエストごとに正しく反映させるためです。

実測:ビルド後に環境変数で上書きする

nuxt build を1回だけ行い、同じ .output を環境変数だけ変えて起動しました。

npx nuxt build
node .output/server/index.mjs   # 環境変数なし
{"apiSecret":"(空)","db":{"host":"localhost","port":5432},"dbPortType":"number","public":{"apiBase":"/api","siteName":"備品管理デモ",…}}
NUXT_API_SECRET=runtime-secret-9876 \
NUXT_DB_HOST=db.internal \
NUXT_DB_PORT=6543 \
NUXT_PUBLIC_API_BASE=https://api.example.com \
node .output/server/index.mjs
{"apiSecret":"runt…(19文字)","db":{"host":"db.internal","port":6543},"dbPortType":"number","public":{"apiBase":"https://api.example.com","siteName":"備品管理デモ",…}}

再ビルドせずに、秘密の値・ネストした値・public の値がすべて差し替わりました。 NUXT_DB_PORT=6543 は文字列ではなく数値 6543 として入っています(Nuxtは環境変数の値を自動で型変換して読みます)。

これが runtimeConfig の最大の利点で、1つのビルド成果物(Dockerイメージなど)を、開発・検証・本番で環境変数だけ変えて使い回せます。

「NUXT_の環境変数が効かない」原因一覧

# 原因 症状(実測) 直し方
1 nuxt.config にキーを定義していない NUXT_NOT_DEFINED_KEY を渡してもキーが存在しない 既定値つきでキーを定義
2 環境変数名の変換を間違えた NUXT_APISECRET などは無視 NUXT_API_SECRET
3 nuxt.config で別名の process.env を読んでいる 実行時に変えてもビルド時の値のまま 同名の NUXT_ 変数で上書き
4 本番で .env が読まれると思っている 本番起動では空のまま サーバーの環境変数で渡す
5 NITRO_ の同名変数が残っている NUXT_ の値が使われない どちらかに統一
6 値の型が変わった 数値のつもりが文字列 起動時に検証する
7 nuxt generate の静的サイト 実行時の上書きがない SSRにするか生成時に渡す

原因1:nuxt.config にキーを定義していない

NUXT_NOT_DEFINED_KEY=hello node .output/server/index.mjs
"notDefined":"(存在しない)"

公式ドキュメントにも「runtimeConfig のカスタム変数は nuxt.config で定義する必要がある」とあります。任意の環境変数がアプリに入り込まないようにするための仕様です。モジュールの設定(例:nuxt-auth-utils の session.cookie.secure)も同じで、nuxt.config に session: { cookie: { secure: true } } と書いた後で初めて NUXT_SESSION_COOKIE_SECURE=false が効きました。書かずに環境変数だけ渡した場合は反映されませんでした。

原因2:キー名の変換を間違えた

NUXT_APISECRET=x1 NUXT_apiSecret=x2 API_SECRET=x3 NUXT_DB__HOST=x4 node .output/server/index.mjs
{"apiSecret":"(空)","db":{"host":"localhost","port":5432},…}

4つとも反映されませんでした。 camelCase の大文字の前と、ネストの区切りに _ を1つずつ入れます。

キー 正しい環境変数
apiSecret NUXT_API_SECRET
orderWebhookSecret のような長い名前 NUXT_ORDER_WEBHOOK_SECRET
db.host NUXT_DB_HOST
public.apiBase NUXT_PUBLIC_API_BASE
public.analytics.siteId NUXT_PUBLIC_ANALYTICS_SITE_ID

app で始まるキー(runtimeConfig.app や appSecret)は Nuxt が予約しているため使いません。

原因3:nuxt.config で process.env を読むとビルド時に固定される

// nuxt.config.ts(直す前)
runtimeConfig: {
  buildTimeValue: process.env.MY_BUILD_VALUE || 'unset',
}
MY_BUILD_VALUE=build-A npx nuxt build
MY_BUILD_VALUE=runtime-B node .output/server/index.mjs
"buildTimeValue":"build-A"

実行時に runtime-B を渡しても、ビルド時の build-A のままでした。 nuxt.config.ts はビルドのときに1回だけ評価され、その結果が成果物に書き込まれるためです。同じキーを実行時に変えたいときは、対応する名前 NUXT_BUILD_TIME_VALUE を渡します。

NUXT_BUILD_TIME_VALUE=runtime-C node .output/server/index.mjs
"buildTimeValue":"runtime-C"

直し方は「nuxt.config では空の既定値だけ書き、値はすべて NUXT_ の環境変数で渡す」です。公式ドキュメントも、別名の環境変数を既定値に使うと「ビルド時にしか動かず実行時に壊れる」と注意しています。

原因4:.env は本番(node .output/server/index.mjs)では読まれない

.env に NUXT_API_SECRET=from-dotenv-file-1234 を書き、起動方法ごとに確かめました。

起動方法 .env の値(実測)
npx nuxt dev 読まれた(from…(21文字))
npx nuxt preview 読まれた(ただし警告あり)
node .output/server/index.mjs 読まれない((空))
node --env-file=.env .output/server/index.mjs 読まれた(Node.js 20.6以降の標準機能)

nuxt preview を実行したときの表示(実行結果の抜粋):

|  You are previewing a Nuxt app. In production, do not use this CLI.
|  Instead, run node server/index.mjs directly.
…
•  Loading .env. This will not be loaded when running the server in production.

開発中は .env で動いていたのに、本番サーバーに置いたら値が空になる、というのはこれが原因です。 本番では、systemd の Environment=、Docker の -e / env_file、ホスティングサービスの環境変数設定画面などで渡します。.env ファイルそのものの管理方法(Gitに入れない、共有のしかた)は .env の基本と同じ考え方です。

原因5:NITRO_ の同名変数が優先される

NITRO_API_SECRET=nitro-secret-5555 NUXT_API_SECRET=nuxt-secret-1111 node .output/server/index.mjs
"apiSecret":"nitr…(17文字)"

NITRO_ で始まる同名の変数があると、そちらが使われました。 以前の設定が残っていないか、env | grep -E '^(NUXT|NITRO)_' で確認すると早く見つかります。

原因6:値の型が変わってしまう

NUXT_DB_PORT=abc node .output/server/index.mjs
"db":{"host":"localhost","port":"abc"},"dbPortType":"string"

型定義上は number でも、実行時の値は検証されません。 数値に見える値は数値、true / false は真偽値、JSONはオブジェクトとして読まれます(NUXT_DB='{"host":"json-host","port":1}' でオブジェクトごと入ることも確認)。大事な値は、server/plugins/ などで起動時に確認しておくと安全です。

// server/plugins/validate-config.ts(起動時に1回だけ確認する例)
export default defineNitroPlugin(() => {
  const config = useRuntimeConfig()
  if (!config.apiSecret) {
    console.warn('[config] NUXT_API_SECRET が設定されていません')
  }
  if (!Number.isInteger(config.db.port)) {
    throw new Error(`[config] NUXT_DB_PORT が数値ではありません: ${config.db.port}`)
  }
})

実行結果:

# NUXT_API_SECRET なしで起動 → 警告を出して起動は続く
[config] NUXT_API_SECRET が設定されていません
Listening on http://0.0.0.0:7460

# NUXT_DB_PORT=abc で起動 → 例外でプロセスが終了する
Error: [config] NUXT_DB_PORT が数値ではありません: abc
    at …/.output/server/chunks/nitro/nitro.mjs:5589:11
    at runNitroPlugins (…)

誤った設定のまま動き続けるより、起動時に止まるほうが原因にすぐ気づけます。

原因7:nuxt generate(静的出力)では実行時の上書きがない

nuxt generate はHTMLを生成して .output/public に書き出し、実行時のサーバーを持ちません。そのため public の値は生成したときの値でHTMLに入ります。静的サイトで環境ごとに値を変える場合は、環境ごとに NUXT_PUBLIC_… を渡して生成し直します。

publicに秘密を置くとHTMLに出る(実測)

public.mapKey に値を入れて起動し、ページのHTMLを取得しました。

NUXT_API_SECRET=server-only-secret-777 \
NUXT_PUBLIC_MAP_KEY=demo-map-key-ABC123 \
node .output/server/index.mjs

curl -s http://localhost:3000/config | grep -o 'window.__NUXT__.config=[^<]*'
window.__NUXT__.config={public:{apiBase:"/api",siteName:"備品管理デモ",mapKey:"demo-map-key-ABC123",auth:{loadStrategy:"server-first"}},app:{baseURL:"/",buildId:"932bb490-…",buildAssetsDir:"/_nuxt/",cdnURL:""}}
値 HTML内の出現回数(実測)
server-only-secret-777(トップレベル apiSecret) 0回
demo-map-key-ABC123(public.mapKey) 1回

public の中身は、ページを開いた人なら誰でも読めます。 ブラウザから直接使う前提で発行された「公開用のキー」(利用元ドメインを制限できる地図APIキーなど)だけを置き、それ以外はトップレベルに置いてサーバーAPI経由で使います。

値の例 置き場所
DBパスワード、外部サービスのシークレットキー、署名用の鍵 トップレベル(サーバーのみ)
自社APIのURL、サイト名、公開用の分析ID public
ボタンの色、表示するメニュー app.config.ts

app.configとの違い

// app/app.config.ts(ビルド時に決まる公開設定。環境変数では上書きできない)
export default defineAppConfig({
  theme: { primaryColor: '#1e40af' },
  features: { darkMode: true },
})
比べる点 runtimeConfig app.config
値が決まるタイミング 実行時(環境変数で上書き) ビルド時
秘密を置けるか トップレベルなら置ける 置けない(クライアントに含まれる)
読み方 useRuntimeConfig() useAppConfig()
実行中の変更 しない前提 updateAppConfig() でリアクティブに変更できる
開発時の反映 nuxt.config の変更でdevサーバーが再起動 HMR(保存するとすぐ反映)に対応
値の種類 JSONにできる値のみ(関数やMap・Setは不可) 設定オブジェクト(型推論が効く)

「環境によって変わる値」は runtimeConfig、「アプリの見た目や機能の設定」は app.config と覚えると迷いません。

型定義

Nuxt は nuxt.config.ts の既定値から型を自動生成します。今回のビルドで生成された .nuxt/types/runtime-config.d.ts の抜粋です。

interface SharedRuntimeConfig {
  apiSecret: string,
  db: {
    host: string,
    port: number,
  },
  …
}

既定値を '' や 0 のように正しい型で書いておけば、useRuntimeConfig() の戻り値に自動で型が付きます。 既定値では表せない型('dev' | 'prod' のような限定した文字列など)を付けたいときは、手動で型を追加します。

// types/runtime-config.d.ts(プロジェクト内の任意の .d.ts)
declare module 'nuxt/schema' {
  interface RuntimeConfig {
    apiSecret: string
  }
  interface PublicRuntimeConfig {
    appEnv: 'development' | 'staging' | 'production'
  }
}
export {}

ただし原因6のとおり、型は実行時の値を保証しない点に注意します。

Docker・CIでの渡し方

Docker

ビルド済みの .output を入れたイメージに、docker run -e で渡しました(実行結果)。

docker run -d -p 7466:3000 \
  -e NUXT_SESSION_PASSWORD=… \
  -e NUXT_API_SECRET=docker-secret-42 \
  -e NUXT_PUBLIC_SITE_NAME='Dockerで起動' \
  nuxt-app

curl -s http://localhost:7466/api/config-check
{"apiSecret":"dock…(16文字)",…,"public":{"apiBase":"/api","siteName":"Dockerで起動",…}}

秘密の値は Dockerfile の ENV や ARG に書かず、起動時に渡します。 ENV に書くとイメージの履歴(docker history)に残るためです。Docker Compose では次のように書けます。

# compose.yaml
services:
  web:
    image: nuxt-app
    ports:
      - "3000:3000"
    env_file:
      - ./production.env   # NUXT_API_SECRET=… などを書いたファイル(Gitに入れない)
    environment:
      NUXT_PUBLIC_API_BASE: https://api.example.com

CI(GitHub Actions など)

タイミング 渡す値 理由
ビルド(nuxt build) 基本は不要 runtimeConfig は実行時に上書きできる
ビルド(nuxt generate) NUXT_PUBLIC_… 静的HTMLに生成時の値が入る
デプロイ先の実行環境 NUXT_… すべて サーバー起動時に読まれる
# .github/workflows/build.yml(抜粋)
- run: npm ci
- run: npx nuxt build        # 秘密はここでは渡さない
# 秘密はデプロイ先(コンテナ実行環境やホスティングの設定画面)に登録する

ビルドのときに秘密を渡さない構成にしておくと、成果物に秘密が入り込む心配がなくなります。

設定を追加するときのチェックリスト

チェック 確認方法
nuxt.config の runtimeConfig にキーと既定値がある 型が自動生成される
秘密はトップレベル、公開してよい値だけ public HTMLを grep して秘密が出ない
環境変数名は NUXT_ +大文字スネークケース 起動後にサーバーAPIやログで確認
nuxt.config で別名の process.env を読んでいない ビルド後に値を変えて起動し直す
本番で .env に頼っていない 環境変数なしで起動したときの値を確認
NITRO_ の同名変数が残っていない env | grep -E '^(NUXT|NITRO)_'
大事な値は起動時に検証している 誤った値で起動してみる

よくある質問

Q. process.env を画面のコードで直接使えますか? A. サーバーで描画している間は読めますが、ブラウザでは process.env の値がないため、同じコードの結果がサーバーとブラウザで食い違います。画面では useRuntimeConfig().public を使うのが確実です。サーバーのコード(server/)で process.env を直接読むことはできますが、runtimeConfig に集めておくと型と既定値を一か所で管理できます。

Q. NUXT_PUBLIC_ の値を実行時に変えると、ブラウザにも新しい値が届きますか? A. SSR(Nodeサーバー)で配信している場合は届きます。実測でも、ビルドし直さずに実行時に渡した NUXT_PUBLIC_MAP_KEY がHTML内の window.__NUXT__.config に入りました。nuxt generate の静的サイトでは生成時の値になります。

Q. .env.production のようなファイルを自動で読み分けられますか? A. Nuxt CLI は --dotenv オプションで読み込むファイルを指定できます(例:npx nuxt build --dotenv .env.staging)。ただし、これも CLI を使うときの話で、node .output/server/index.mjs での本番起動では読まれません。本番は環境変数で渡すのが基本です。

まとめ

  • runtimeConfig のトップレベルはサーバーだけ、public はブラウザにも届く。秘密はトップレベルに置きます。
  • 環境変数名は NUXT_ +大文字スネークケース(apiSecret→NUXT_API_SECRET、db.host→NUXT_DB_HOST)。
  • nuxt.config にキーを定義しないと上書きされません。既定値(空文字でも可)を必ず書きます。
  • nuxt.config で読んだ process.env はビルド時に固定。実行時に変えるなら同名の NUXT_ 変数で渡します。
  • .env は本番の node .output/server/index.mjs では読まれません(nuxt preview は読むが本番用ではない)。
  • public に置いた値は window.__NUXT__.config としてHTMLに出ることを実測で確認しました。
  • 1回のビルドを環境変数だけ変えて使い回せるのが runtimeConfig の強みです。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Nuxt runtimeConfigと環境変数|NUXT_が効かない原因を実測」

株式会社RJCでは、設定値の置き場所や環境ごとの切り替えといった「動かす前に決めておくこと」を、プロジェクトの中で仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?