diff --git a/docs/content/1.guide/2.auto-imports.md b/docs/content/1.guide/2.auto-imports.md
index 9a841b405e..4cc07d1fe7 100644
--- a/docs/content/1.guide/2.auto-imports.md
+++ b/docs/content/1.guide/2.auto-imports.md
@@ -11,7 +11,8 @@ Nitro is using [unjs/unimport](https://github.com/unjs/unimport) to auto import
- `defineCachedFunction(fn, options)`{lang=ts} / `cachedFunction(fn, options)`{lang=ts}
- `defineCachedEventHandler(handler, options)`{lang=ts} / `cachedEventHandler(handler, options)`{lang=ts}
- `defineRenderHandler(handler)`{lang=ts}
-- `useRuntimeConfig()`{lang=ts}
+- `useRuntimeConfig(event?)`{lang=ts}
+- `useAppConfig(event?)`{lang=ts}
- `useStorage(base?)`{lang=ts}
- `useNitroApp()`{lang=ts}
- `defineNitroPlugin(plugin)`{lang=ts}
diff --git a/playground/routes/index.ts b/playground/routes/index.ts
index 2b5a032a89..4f39705a87 100644
--- a/playground/routes/index.ts
+++ b/playground/routes/index.ts
@@ -1,3 +1,3 @@
-import { eventHandler } from "h3";
-
-export default eventHandler(() => "
Hello Nitro!
");
+export default eventHandler(() => {
+ return "Hello Nitro!
";
+});
diff --git a/src/runtime/app.ts b/src/runtime/app.ts
index 1eebf7065c..391960ad9d 100644
--- a/src/runtime/app.ts
+++ b/src/runtime/app.ts
@@ -60,6 +60,8 @@ function createNitroApp(): NitroApp {
// A generic event handler give nitro acess to the requests
h3App.use(
eventHandler((event) => {
+ // Init nitro context
+ event.context.nitro = event.context.nitro || {};
// Support platform context provided by local fetch
const envContext = (event.node.req as any).__unenv__;
if (envContext) {
diff --git a/src/runtime/config.ts b/src/runtime/config.ts
index d54a631be0..f9ada9bddb 100644
--- a/src/runtime/config.ts
+++ b/src/runtime/config.ts
@@ -1,57 +1,103 @@
import destr from "destr";
import { snakeCase } from "scule";
-import { appConfig as _appConfig } from "#internal/nitro/virtual/app-config";
+import { klona } from "klona";
+import { H3Event } from "h3";
+import { appConfig as _inlineAppConfig } from "#internal/nitro/virtual/app-config";
-// Runtime config
-const _runtimeConfig = process.env.RUNTIME_CONFIG as any;
+// Static runtime config inlined by nitro build
+const _inlineRuntimeConfig = process.env.RUNTIME_CONFIG as any;
const ENV_PREFIX = "NITRO_";
const ENV_PREFIX_ALT =
- _runtimeConfig.nitro.envPrefix ?? process.env.NITRO_ENV_PREFIX ?? "_";
-overrideConfig(_runtimeConfig);
+ _inlineRuntimeConfig.nitro.envPrefix ?? process.env.NITRO_ENV_PREFIX ?? "_";
-const runtimeConfig = deepFreeze(_runtimeConfig);
-export default runtimeConfig; // TODO: Remove in next major version
-export const useRuntimeConfig = () => runtimeConfig;
+// Runtime config
+const _sharedRuntimeConfig = _deepFreeze(
+ _applyEnv(klona(_inlineRuntimeConfig))
+);
+export function useRuntimeConfig(event?: H3Event) {
+ // Backwards compatibility with ambient context
+ if (!event) {
+ return _sharedRuntimeConfig;
+ }
+ // Reuse cached runtime config from event context
+ if (event.context.nitro.runtimeConfig) {
+ return event.context.nitro.runtimeConfig;
+ }
+ // Prepare runtime config for event context
+ const runtimeConfig = klona(_inlineRuntimeConfig);
+ _applyEnv(runtimeConfig);
+ event.context.nitro.runtimeConfig = runtimeConfig;
+ return runtimeConfig;
+}
// App config
-const appConfig = deepFreeze(_appConfig);
-export const useAppConfig = () => appConfig;
+const _sharedAppConfig = _deepFreeze(klona(_inlineAppConfig));
+export function useAppConfig(event?: H3Event) {
+ // Backwards compatibility with ambient context
+ if (!event) {
+ return _sharedAppConfig;
+ }
+ // Reuse cached app config from event context
+ if (event.context.nitro.appConfig) {
+ return event.context.nitro.appConfig;
+ }
+ // Prepare app config for event context
+ const appConfig = klona(_inlineAppConfig);
+ event.context.nitro.appConfig = appConfig;
+ return appConfig;
+}
// --- Utils ---
-function getEnv(key: string) {
+function _getEnv(key: string) {
const envKey = snakeCase(key).toUpperCase();
return destr(
process.env[ENV_PREFIX + envKey] ?? process.env[ENV_PREFIX_ALT + envKey]
);
}
-function isObject(input: unknown) {
+function _isObject(input: unknown) {
return typeof input === "object" && !Array.isArray(input);
}
-function overrideConfig(obj: object, parentKey = "") {
+function _applyEnv(obj: object, parentKey = "") {
for (const key in obj) {
const subKey = parentKey ? `${parentKey}_${key}` : key;
- const envValue = getEnv(subKey);
- if (isObject(obj[key])) {
- if (isObject(envValue)) {
+ const envValue = _getEnv(subKey);
+ if (_isObject(obj[key])) {
+ if (_isObject(envValue)) {
obj[key] = { ...obj[key], ...envValue };
}
- overrideConfig(obj[key], subKey);
+ _applyEnv(obj[key], subKey);
} else {
obj[key] = envValue ?? obj[key];
}
}
+ return obj;
}
-function deepFreeze(object: Record) {
+function _deepFreeze(object: Record) {
const propNames = Object.getOwnPropertyNames(object);
for (const name of propNames) {
const value = object[name];
if (value && typeof value === "object") {
- deepFreeze(value);
+ _deepFreeze(value);
}
}
return Object.freeze(object);
}
+
+// --- Deprecated default export ---
+// TODO: Remove in next major version
+export default new Proxy(Object.create(null), {
+ get: (_, prop) => {
+ console.warn(
+ "Please use `useRuntimeConfig()` instead of accessing config directly."
+ );
+ const runtimeConfig = useRuntimeConfig();
+ if (prop in runtimeConfig) {
+ return runtimeConfig[prop];
+ }
+ return undefined;
+ },
+});
diff --git a/test/fixture/middleware/config.ts b/test/fixture/middleware/config.ts
new file mode 100644
index 0000000000..28158e4dad
--- /dev/null
+++ b/test/fixture/middleware/config.ts
@@ -0,0 +1,6 @@
+process.env.NITRO_DYNAMIC = "from-env";
+
+export default eventHandler((event) => {
+ const appConfig = useAppConfig(event);
+ appConfig.dynamic = "from-middleware";
+});
diff --git a/test/fixture/nitro.config.ts b/test/fixture/nitro.config.ts
index 22e8c62a0b..b06f347082 100644
--- a/test/fixture/nitro.config.ts
+++ b/test/fixture/nitro.config.ts
@@ -22,6 +22,10 @@ export default defineNitroConfig({
],
appConfig: {
"nitro-config": true,
+ dynamic: "initial",
+ },
+ runtimeConfig: {
+ dynamic: "initial",
},
appConfigFiles: ["~/server.config.ts"],
publicAssets: [
diff --git a/test/fixture/routes/app-config.ts b/test/fixture/routes/app-config.ts
deleted file mode 100644
index 39e7b94821..0000000000
--- a/test/fixture/routes/app-config.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export default eventHandler(() => {
- const appConfig = useAppConfig();
-
- return {
- appConfig,
- };
-});
diff --git a/test/fixture/routes/config.ts b/test/fixture/routes/config.ts
new file mode 100644
index 0000000000..9097e19f5c
--- /dev/null
+++ b/test/fixture/routes/config.ts
@@ -0,0 +1,14 @@
+const sharedAppConfig = useAppConfig();
+const sharedRuntimeConfig = useRuntimeConfig();
+
+export default eventHandler((event) => {
+ const appConfig = useAppConfig(event);
+ const runtimeConfig = useRuntimeConfig(event);
+
+ return {
+ sharedAppConfig,
+ appConfig,
+ runtimeConfig,
+ sharedRuntimeConfig,
+ };
+});
diff --git a/test/tests.ts b/test/tests.ts
index cce4c4de88..6122981e4a 100644
--- a/test/tests.ts
+++ b/test/tests.ts
@@ -308,19 +308,40 @@ export function testNitro(
expect(data.url).toBe("/api/echo?foo=bar");
});
- it("app config", async () => {
+ it("config", async () => {
const { data } = await callHandler({
- url: "/app-config",
+ url: "/config",
});
- expect(data).toMatchInlineSnapshot(`
- {
- "appConfig": {
- "app-config": true,
- "nitro-config": true,
- "server-config": true,
+ expect(data).toMatchObject({
+ appConfig: {
+ dynamic: "from-middleware",
+ "app-config": true,
+ "nitro-config": true,
+ "server-config": true,
+ },
+ runtimeConfig: {
+ dynamic: "from-env",
+ app: {
+ baseURL: "/",
+ },
+ },
+ sharedAppConfig: {
+ dynamic: "initial",
+ "app-config": true,
+ "nitro-config": true,
+ "server-config": true,
+ },
+ sharedRuntimeConfig: {
+ dynamic:
+ // TODO
+ ctx.preset.includes("cloudflare") || ctx.preset === "nitro-dev"
+ ? "initial"
+ : "from-env",
+ app: {
+ baseURL: "/",
},
- }
- `);
+ },
+ });
});
if (ctx.nitro!.options.timing) {