In Nuxt 4, when do you use `runtimeConfig` versus `app.config.ts`, and which of their values reach the browser?
answer
- one varies per deployment
- the other is fixed at build
- only one section is public
- sent in the HTML versus bundled
- never a secret in app config
basics
~20 sruntimeConfig holds values that differ per deployment and can be overridden by NUXT_ environment variables; only its public section reaches the browser. app.config.ts holds build-time, non-secret settings such as theme or title, and all of it is bundled into the client.
solid answer
~50 sUse `runtimeConfig` in `nuxt.config.ts` for values that change between deployments: API keys, base URLs, a public site URL. Top-level keys are **private**: the server sees them through `useRuntimeConfig()`, the browser does not. Keys under `runtimeConfig.public` are sent to the browser with every server-rendered page, and both kinds can be overridden at runtime by `NUXT_` environment variables without a rebuild. Use `app/app.config.ts` with `defineAppConfig` for public settings decided at build time, such as a theme colour, a site title or feature toggles; it is compiled into the client bundle, is read with `useAppConfig()`, cannot be overridden by environment variables, and must never hold a secret. In the browser, reading a private runtime key returns `undefined`, and in development Nuxt warns and suggests moving it to `public`, which is only right for values that are safe to publish.
code
ts · 12 lines// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
analyticsApiKey: '', // server only; NUXT_ANALYTICS_API_KEY
public: { siteUrl: 'http://localhost:3000' }, // browser too; NUXT_PUBLIC_SITE_URL
},
})
// app/app.config.ts
export default defineAppConfig({
analyticsBanner: { position: 'bottom' }, // bundled, public, fixed per build
})go deeper
Recall that runtimeConfig has private keys and a public section, that app.config.ts is for public build-time settings, and which composable reads each.
Explain how each reaches the browser, public config in the page HTML versus app config in the bundle, and why only runtimeConfig responds to NUXT_ environment variables.
Show how you classify a new setting by environment variance and sensitivity, and why the dev warning's advice to move a key to public must be questioned.
Set the rule for your teams on what may be public, what varies per environment, and how prerendered pages and one-build-many-environments deployments interact with it.
## Two configuration surfaces Nuxt 4 gives an app two places for configuration that code reads at runtime. They look similar and are easy to mix up, which is why interviewers ask. **`runtimeConfig`** is an option in `nuxt.config.ts`. It is meant for values that **vary per deployment**: an analytics API key, a service base URL, the public URL of the site. Its top-level keys are **private** to the server. The nested `public` object is also sent to the browser. Every key can be overridden at runtime by an environment variable with the `NUXT_` prefix, so the same build can run with different values. **`app.config.ts`** lives in the source directory, `app/app.config.ts` in Nuxt 4, and default-exports `defineAppConfig({...})`. It is meant for **public settings decided at build time**: a theme, a site title, which optional widgets to show. Its contents are compiled into the client bundle and cannot be overridden by environment variables. ## Side by side | | `runtimeConfig` | `app.config.ts` | |---|---|---| | Defined in | `nuxt.config.ts` | `app/app.config.ts` | | Read with | `useRuntimeConfig()` | `useAppConfig()` | | Private values | top-level keys, server only | none: everything is public | | How the browser gets it | `public` sent with each server-rendered page | bundled into the client JavaScript | | Environment override | yes, `NUXT_` and `NUXT_PUBLIC_` variables | no | | Change without rebuild | yes, set the variable and restart | no | | Reactive in the app | yes | yes | ## Where each value is visible - **On the server**, `useRuntimeConfig()` returns everything: private keys and `public`. Server routes should call `useRuntimeConfig(event)`. - **In the browser**, `useRuntimeConfig()` returns only `public` (plus `app`, which Nuxt uses internally). The public values arrive in a script in each server-rendered page, as `window.__NUXT__.config`, and templates can read them as `$config.public`. - **Reading a private key in the browser** gives `undefined`. In development Nuxt also logs a diagnostic saying the key is not available on the client and suggesting you move it under `runtimeConfig.public`. That advice is right for a site URL and wrong for a secret. - **`app.config`** is visible everywhere, because it is part of the code. ## Choosing in practice For a site with an analytics module: 1. `runtimeConfig.analyticsApiKey`: private, set by `NUXT_ANALYTICS_API_KEY` in each environment, read only in server code. 2. `runtimeConfig.public.siteUrl`: public, set by `NUXT_PUBLIC_SITE_URL`, used for canonical links on both sides. 3. `app.config.ts`: `{ analyticsBanner: { position: 'bottom' }, theme: { primary: 'teal' } }`, fixed per build and fine to publish. A useful test: *would a different environment need a different value?* If yes, it belongs in `runtimeConfig`. *Would it be harmful to publish?* If yes, it must be a private `runtimeConfig` key and nothing else. ## Details that trip people up - `runtimeConfig` values are serialised on their way to the server build, so functions, `Map`s and class instances do not survive; put that logic in a plugin. - `updateAppConfig` can change app config values while the app runs; the built bundle itself is unchanged. - A prerendered page contains the public runtime config it was generated with, so changing `NUXT_PUBLIC_*` later does not change already generated HTML. - Both surfaces are typed automatically from their values, and can be augmented by hand when needed. ## Where modules fit in Modules use the same two surfaces. A module that needs its options at runtime copies them into `runtimeConfig`, public or private, because module options themselves exist only at build time. A module can also contribute defaults to app config, typing what it accepts through the `AppConfigInput` interface so that `app.config.ts` entries for it are checked. The same rule applies to both: anything a module places in `runtimeConfig.public` or app config is published, so a module that asks for an API key must keep it at the top level of `runtimeConfig`. ## A quick classification exercise | Setting | Surface | Why | |---|---|---| | analytics API key | private `runtimeConfig` | secret, differs per environment | | public site URL | `runtimeConfig.public` | safe, differs per environment | | banner position, brand colour | `app.config.ts` | safe, same everywhere, fixed per build | | feature toggle flipped per environment | `runtimeConfig.public` | must change without a rebuild | Interviewers often ask the last row to see whether a candidate picks by *who may see it* and *when it changes*, rather than by habit.
- Why can a public runtime config value change without rebuilding while an app.config value cannot?The public runtime config is produced by the running server for each page, from the build's defaults plus any `NUXT_PUBLIC_` environment variables, and sent in the HTML. `app.config` is compiled into the JavaScript bundle during the build, so changing it means building again. Prerendered pages are the exception: their HTML already contains the values from generation time.
- What does useRuntimeConfig return in a component during server rendering versus in the browser?During server rendering it returns the full config, private keys included, so server-only code paths can read the key. In the browser it returns only `public` and Nuxt's internal `app` section. Code that reads a private key must therefore run only on the server, or the browser sees `undefined`.
app.config is printed on the product box at the factory, public runtime config is a price label the shop sticks on each morning, and private runtime config stays in the shop's safe: changing a label needs no new boxes, and nothing in the safe is ever stuck on a box.
saying these in an interview costs you the question
- Keys at the top level of runtimeConfig are sent to the browser too.
- app.config.ts values can be overridden with NUXT_ environment variables.
- app.config.ts is safe for secrets because it is not part of runtimeConfig.
- Changing a public runtime config value always requires a rebuild.
- When the dev warning says a key is missing on the client, moving it to public is always the fix.