nuxt.config, runtimeConfig, TypeScriptEz a modul egyetlen kérdés köré épül: hol lakik egy érték, és ki látja? Négy szint van, és a köztük lévő különbség nem stílus kérdése — az egyiken elhelyezett API-kulcs a felhasználó böngészőjében köt ki. Végigvesszük a négyet, megnézzük, hogyan ellenőrzöd, hogy nem szivárogtál, és hogy a .env-től a Cloudflare-secretig mi hova kerül.
env-be), de a tárolásuk és a láthatóságuk gyökeresen más.| Szint | Hol írod | Ki látja | Változtatható deploy nélkül? |
|---|---|---|---|
| ① Build-time | nuxt.config.ts, app.config.ts | A kliens is (bele van fordítva) | Nem |
② runtimeConfig (felső szint) | nuxt.config.ts + env | Csak a szerver | Igen — env-változóval |
② runtimeConfig.public | ugyanott | A kliens is | Igen |
③ Cloudflare vars | wrangler.jsonc | Aki a repót vagy a dashboardot látja | Deployjal |
| ④ Cloudflare secret | wrangler secret put | Senki — csak a futó kód | Igen |
runtimeConfig.public alá, mert ott „működik a kliensről is”. Onnantól a kulcs benne van a HTML-ben és a JS-bundle-ben — bárki megnézheti. A public szó azt jelenti: nyilvános. Ha egy értéknek a böngészőben nincs keresnivalója, nem kerülhet oda.runtimeConfig a gyakorlatban// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
// ── csak a szerver látja ──────────────────────────────
sessionPassword: '', // NUXT_SESSION_PASSWORD
stripeSecretKey: '', // NUXT_STRIPE_SECRET_KEY
resendApiKey: '', // NUXT_RESEND_API_KEY
upstream: {
baseUrl: '', // NUXT_UPSTREAM_BASE_URL
token: '', // NUXT_UPSTREAM_TOKEN
},
// ── a kliens IS látja ─────────────────────────────────
public: {
appName: 'Scoope', // NUXT_PUBLIC_APP_NAME
siteUrl: '', // NUXT_PUBLIC_SITE_URL
stripePublicKey:'', // NUXT_PUBLIC_STRIPE_PUBLIC_KEY ← a pk_ kezdetű, az OK
sentryDsn: '', // NUXT_PUBLIC_SENTRY_DSN
},
},
})
Az üres string nem véletlen: a kulcsnak léteznie kell a konfigban, hogy az env-változó felül tudja írni. Ha nincs ott, a NUXT_… változó nem csinál semmit — ez a leggyakoribb „miért üres a config?” ok.
runtimeConfig.stripeSecretKey → NUXT_STRIPE_SECRET_KEY
runtimeConfig.upstream.baseUrl → NUXT_UPSTREAM_BASE_URL
runtimeConfig.public.siteUrl → NUXT_PUBLIC_SITE_URL
Prefix mindig NUXT_, a beágyazott szintek aláhúzással, a camelCase pedig nagybetűs, aláhúzásos alakra bomlik. Az értékek automatikusan típusra konvertálódnak — ha a "3000"-et stringként akarod, idézőjelezd a .env-ben.
// szerveroldalon: MINDIG add át az event-et
export default defineEventHandler(async (event) => {
const cfg = useRuntimeConfig(event) // ← az event nem opcionális szeszély
const key = cfg.stripeSecretKey
})
// kliensoldalon: csak a public érhető el
const { public: pub } = useRuntimeConfig()
console.log(pub.siteUrl)
event Workers-en? Mert a környezeti változók nem egy globális process.env-ből jönnek, hanem a kérés kontextusából (a Worker env objektumából). A dokumentáció is azt ajánlja, hogy serverless környezetben add át az eventet — Workers-en ez a különbség aközött, hogy a friss értéket kapod, vagy egy build-time alapértelmezést. Vedd fel szokásnak: a useRuntimeConfig() a server/ alatt mindig kap eventet.runtimeConfig vagy app.config.ts?runtimeConfig | app.config.ts | |
|---|---|---|
| Mikor dől el az érték | Futásidőben | Build-időben |
| Env-változóval felülírható | Igen | Nem |
| Lehet benne titok | Igen (a felső szinten) | Soha — a bundle-be kerül |
| Nem-primitív értékek | Korlátozottan | Igen (objektum, függvény) |
| HMR fejlesztés közben | Nincs | Van |
| Mire való | Kulcsok, URL-ek, környezetenként eltérő értékek | Téma, UI-beállítás, arculat |
app.config.ts a build-időben ismert dolgokra való — a tenantonként eltérő arculat ezért nem ide való. Az futásidőben, a tenant-rekordból jön (4. modul), vagy layerekkel oldható meg, ha tényleg külön build készül tenantonként (14. modul).Négy úton kerülhet titok a kliensbe, és mind a négy csendes:
| Út | Hogyan történik |
|---|---|
public alá tett kulcs | Szándékos, de rossz döntés. „A kliensről is kell hozzáférni” — nem kell, a szerveren keresztül kell. |
Konstans a shared/-ben | A shared/ mindkét oldalra bekerül (2. modul). Egy export const ADMIN_KEY = '…' ott kimegy a böngészőbe. |
| Composable vagy komponens | Minden, ami az app/ alatt van, a kliens-bundle része. Nincs kivétel. |
| Payload | Nem konfiguráció, de ugyanaz a kimenet: a szerveri lekérés eredménye a HTML-be kerül (5. modul). |
# build után nézd meg, hogy a titkaid nincsenek-e a kliens-bundle-ben
npm run build
grep -ril "sk_live\|SESSION_PASSWORD\|BEGIN PRIVATE KEY" .output/public/ || echo "tiszta"
# és konkrét értékre, ha van sejtésed:
grep -ro "sk_test_[A-Za-z0-9]*" .output/public/ | head
sk_live, -----BEGIN, _SECRET, a saját kulcs-prefixeid) bőven elég — a lényeg, hogy legyen, és a build bukjon el, ha talál valamit. A 15. modul bundle-elemzése mellé kerülhet, ugyanabba a lépésbe.vars, secret, binding// wrangler.jsonc
{
"name": "app",
"main": ".output/server/index.mjs",
"compatibility_date": "2026-08-01",
"compatibility_flags": ["nodejs_compat"],
"assets": { "directory": ".output/public", "binding": "ASSETS" },
// ③ NEM titok — látszik a repóban és a dashboardon
"vars": {
"NUXT_PUBLIC_SITE_URL": "https://app.pelda.hu",
"NUXT_PUBLIC_APP_NAME": "Scoope"
},
// + bindingok: nem érték, hanem képesség
"hyperdrive": [{ "binding": "APP_DB", "id": "…" }],
"r2_buckets": [{ "binding": "BUCKET", "bucket_name": "app-files" }],
"kv_namespaces": [{ "binding": "CACHE", "id": "…" }]
}
# ④ a titkok NEM a configba mennek
npx wrangler secret put NUXT_SESSION_PASSWORD
npx wrangler secret put NUXT_STRIPE_SECRET_KEY
npx wrangler secret list
vars-ban van, azt tekintsd nyilvánosnak — a repóban van, a dashboardon látszik, a build-logban is megjelenhet. ② A vars és a bindingok nem öröklődnek a wrangler környezetek között: ha van [env.staging], ott újra fel kell sorolni őket, különben csendben eltűnnek. Ez a Cloudflare-anyag 12. moduljának „non-inheritable keys” szabálya, és tipikusan úgy derül ki, hogy stagingben nincs adatbázis.fejlesztés: .env fájl → process.env → runtimeConfig → useRuntimeConfig()
éles: wrangler secret → Worker env → runtimeConfig → useRuntimeConfig(event)
wrangler vars → Worker env → runtimeConfig → useRuntimeConfig(event)
Vagyis a kódod egyetlen helyről olvas — useRuntimeConfig(event) —, és nem tudja, hogy az érték .env-ből, vars-ból vagy secretből jött. Ez a jó: a titkok kezelése üzemeltetési kérdés marad, nem szivárog be az alkalmazáslogikába.
Nuxt-oldalon a nuxt.config.ts támogat környezet-specifikus blokkokat, amiket a c12 old fel:
export default defineNuxtConfig({
// közös alap
routeRules: { '/api/**': { headers: { 'cache-control': 'no-store' } } },
$development: {
devtools: { enabled: true },
},
$production: {
routeRules: {
'/': { prerender: true },
'/blog/**': { prerender: true },
},
},
// saját, névvel hivatkozott környezet
$env: {
staging: {
runtimeConfig: { public: { siteUrl: 'https://staging.pelda.hu' } },
},
},
})
nuxt build --envName staging
$production/$development a viselkedés különbségeire való (devtools, prerender, source map). Az értékek környezetenkénti eltérése viszont maradjon a runtimeConfig + env-változó úton — mert azt deploy nélkül is állíthatod, és nem kell hozzá külön build. Ha egy staging-URL a build-be van égetve, akkor a staging és a production különböző artefakt, és nem tudod ugyanazt a buildet előléptetni.nuxt.config.ts — mit hovaEgy valós konfig gyorsan kétszáz sorosra hízik. Ez a sorrend segít, hogy később is megtaláld benne, amit keresel:
export default defineNuxtConfig({
// 1. alapok
compatibilityDate: '2026-08-01',
srcDir: 'app', // Nuxt 4-ben ez az alapértelmezés
devtools: { enabled: true },
// 2. modulok — a sorrend SZÁMÍT, egymásra épülhetnek
modules: ['@pinia/nuxt', '@nuxt/image', 'nuxt-auth-utils', '@nuxtjs/i18n'],
// 3. amit az app tölt be
css: ['~/assets/css/main.css'],
// 4. auto-import finomhangolás (3. modul)
components: [{ path: '~/components', pathPrefix: false }],
imports: { dirs: ['composables/**'] },
// 5. futásidejű értékek (12.2)
runtimeConfig: { /* … */ },
// 6. útvonal-viselkedés (7. modul)
routeRules: { /* … */ },
// 7. szerver és platform (2., 7., 11. modul)
nitro: {
preset: 'cloudflare_module',
storage: { cache: { driver: 'cloudflare-kv-binding', binding: 'CACHE' } },
},
// 8. build-eszközök
vite: { /* … */ },
// 9. környezeti felülírások a végén — így látszik, mi tér el
$development: { /* … */ },
$production: { /* … */ },
})
A Nuxt 4 nagy változása, hogy külön TypeScript-projekteket generál az app-, a szerver-, a megosztott és a builder-kódnak. Neked ehhez egyetlen fájlt kell karbantartanod:
// tsconfig.json — a gyökérben, ennyi
{
"files": [],
"references": [
{ "path": "./.nuxt/tsconfig.app.json" },
{ "path": "./.nuxt/tsconfig.server.json" },
{ "path": "./.nuxt/tsconfig.shared.json" },
{ "path": "./.nuxt/tsconfig.node.json" }
]
}
A haszna konkrét: a szerverkódban nem kapsz DOM-típusokat (tehát nem tudsz véletlenül document-et írni ott), az app-kódban pedig nem látszanak a Nitro-utilok. A 2. modul „melyik kódom hol fut” táblája így a fordító szintjén is érvényesül.
npx wrangler types # worker-configuration.d.ts a wrangler.jsonc alapján
// server/types/runtime-config.d.ts — hogy a runtimeConfig is típusos legyen
declare module 'nuxt/schema' {
interface RuntimeConfig {
sessionPassword: string
stripeSecretKey: string
upstream: { baseUrl: string; token: string }
}
interface PublicRuntimeConfig {
appName: string
siteUrl: string
stripePublicKey: string
}
}
export {}
RuntimeConfig és a PublicRuntimeConfig külön típus, akkor a fordító megfogja, ha kliensoldali kódból a szerver-only kulcsra hivatkozol — vagyis a 12.3-as szivárgás egyik útja fordítási hibává válik, nem code review-kérdéssé.runtimeConfig felső szintjén van — semmi a public alatt, aminek nem valóserver/ alatt minden useRuntimeConfig() kap event-etwrangler secret-ben vannak, nem vars-banvars és a bindingok újra fel vannak sorolva (nem öröklődnek).output/public/ fölött, és a CI bukik, ha találRuntimeConfig típusok deklarálva vannak, hogy a rossz oldali hozzáférés fordítási hiba legyen.env a .gitignore-ban, és van .env.example a kulcsok listájával@nuxt/image a sharp nélkül, i18n, fontkezelés, UI-könyvtárak —, plusz hogyan írj sajátot, ha ismétlődő setupod van.runtimeConfig-ba, de futásidőben üres. Mi a két leggyakoribb ok?
① Az env-változó neve nem stimmel: NUXT_ prefix kell, a beágyazott szintek aláhúzással, a camelCase nagybetűs-aláhúzásos alakban (upstream.baseUrl → NUXT_UPSTREAM_BASE_URL). ② A kulcs nem szerepel a runtimeConfig-ban alapértékkel — ha nincs ott, nincs mit felülírni, és a változó nem csinál semmit.
event a useRuntimeConfig()-nak a szerveren, Workers alatt?
Mert a környezeti változók nem globális process.env-ből jönnek, hanem a kérés kontextusából — a Worker env objektumából. Event nélkül build-time alapértelmezést kaphatsz a friss érték helyett. A Nuxt dokumentációja is ezt ajánlja serverless környezetre.
vars és a secret között Cloudflare-en?
A vars a wrangler.jsonc-ben van: a repóban látszik, a dashboardon olvasható, a build-logban megjelenhet — tekintsd nyilvánosnak. A secret (wrangler secret put) titkosítva tárolódik, és csak a futó kód olvashatja. A kódban mindkettő ugyanoda érkezik, de ami titok, az csak secretbe kerülhet.
Mert akkor a staging és a production különböző artefakt, tehát amit letesztelsz, az nem az, ami élesbe megy. Az értékek környezetenkénti eltérése maradjon runtimeConfig + env-változó úton — így ugyanazt a buildet léptetheted elő, ami a Cloudflare version/deployment modelljének is az alapja.
① runtimeConfig.public alá tett érték. ② Konstans a shared/-ben (mindkét oldalra bekerül). ③ Bármi az app/ alatt — composable, komponens, plugin. ④ A payload: a szerveri lekérés eredménye a HTML-be kerül (5. modul). Ellenőrzés: build után grep a .output/public/ fölött, CI-lépésként.
app.config.ts-t runtimeConfig helyett?
Amikor az érték build-időben ismert, nyilvános, és jól jön a HMR meg a nem-primitív típusok támogatása: téma, UI-beállítás, arculati konstansok. Titkot vagy környezetenként eltérő értéket soha — azt nem lehet env-változóval felülírni, és a bundle-be kerül.