From 4dc23b04ac1a1c1cb36d932f46295572a2b12a32 Mon Sep 17 00:00:00 2001 From: Jan Vollmer Date: Sat, 26 Sep 2026 11:26:02 +0200 Subject: [PATCH 1/4] first test implementation Signed-off-by: Jan Vollmer --- src/build/config.ts | 1 + src/runtime/virtual/app.ts | 11 +++++++-- src/types/config.ts | 49 +++++++++++++++++++++++++++++++------- src/types/global.ts | 1 + 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/src/build/config.ts b/src/build/config.ts index ca7d2c6457..6565f05083 100644 --- a/src/build/config.ts +++ b/src/build/config.ts @@ -24,6 +24,7 @@ export function baseBuildConfig(nitro: Nitro) { _asyncContext: nitro.options.experimental.asyncContext, _tasks: nitro.options.experimental.tasks, _websocket: nitro.options.features.websocket ?? nitro.options.experimental.websocket, + _runtimeBaseURL: nitro.options.features.runtimeBaseURL, }; const replacements = { diff --git a/src/runtime/virtual/app.ts b/src/runtime/virtual/app.ts index a00f13b751..1df746ddf8 100644 --- a/src/runtime/virtual/app.ts +++ b/src/runtime/virtual/app.ts @@ -1,7 +1,7 @@ import "./_runtime_warn.ts"; import type { NitroApp } from "nitro/types"; import type { ServerRequest } from "srvx"; -import { H3Core } from "h3"; +import { H3Core, requestWithBaseURL } from "h3"; import errorHandler from "#nitro/virtual/error-handler"; export function createNitroApp(): NitroApp { @@ -10,7 +10,10 @@ export function createNitroApp(): NitroApp { return errorHandler(error, event); }, }); - const captureError: NonNullable = (error, errorCtx) => { + const captureError: NonNullable = ( + error, + errorCtx, + ) => { if (errorCtx?.event) { const errors = errorCtx.event.req.context?.nitro?.errors; if (errors) { @@ -22,6 +25,10 @@ export function createNitroApp(): NitroApp { fetch: (req: ServerRequest) => { req.context ||= {}; req.context.nitro = req.context.nitro || { errors: [] }; + if (import.meta._runtimeBaseURL) { + const baseURL = process.env.NITRO_APP_BASE_URL ?? ""; + req = requestWithBaseURL(req, baseURL); + } return h3App.fetch(req); }, h3: h3App, diff --git a/src/types/config.ts b/src/types/config.ts index f7cb88d810..85cabb192d 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -1,5 +1,10 @@ import type commonjs from "@rollup/plugin-commonjs"; -import type { C12InputConfig, ConfigWatcher, DotenvOptions, ResolvedConfig } from "c12"; +import type { + C12InputConfig, + ConfigWatcher, + DotenvOptions, + ResolvedConfig, +} from "c12"; import type { WatchConfigOptions } from "c12"; import type { ChokidarOptions } from "chokidar"; import type { CompatibilityDateSpec, CompatibilityDates } from "compatx"; @@ -7,7 +12,11 @@ import type { LogLevel } from "consola"; import type { ConnectorName, ConnectorOptions } from "db0"; import type { NestedHooks } from "hookable"; import type { ProxyServerOptions } from "httpxy"; -import type { PresetName, PresetNameInput, PresetOptions } from "../presets/index.ts"; +import type { + PresetName, + PresetNameInput, + PresetOptions, +} from "../presets/index.ts"; import type { TSConfig } from "pkg-types"; import type { Preset as UnenvPreset } from "unenv"; import type { BuiltinDriverName, BuiltinDriverOptions } from "unstorage"; @@ -31,7 +40,9 @@ import type { RollupConfig } from "./build.ts"; import type { NitroRouteConfig, NitroRouteRules } from "./route-rules.ts"; import type { JsonValue, SerializableOptions } from "./_utils.ts"; -type RollupCommonJSOptions = NonNullable[0]>; +type RollupCommonJSOptions = NonNullable< + Parameters[0] +>; /** * Fully resolved Nitro options available on `nitro.options`. @@ -314,6 +325,11 @@ export interface NitroOptions extends PresetOptions { * Enable WebSocket support. */ websocket?: boolean; + + /** + * Enable NITRO_APP_BASE_URL env var to take effect at runtime + */ + runtimeBaseURL?: boolean; }; /** @@ -643,7 +659,10 @@ export interface NitroOptions extends PresetOptions { * * @see https://nitro.build/config#routes */ - routes: Record>; + routes: Record< + string, + string | Omit + >; /** * Path(s) to custom runtime error handler(s). @@ -684,7 +703,9 @@ export interface NitroOptions extends PresetOptions { /** Fail the build when a route cannot be prerendered. */ failOnError?: boolean; /** Patterns (string, RegExp, or function) of routes to skip. */ - ignore?: Array undefined | null | boolean)>; + ignore?: Array< + string | RegExp | ((path: string) => undefined | null | boolean) + >; /** Skip prerendering assets without a base URL prefix. */ ignoreUnprefixedPublicAssets?: boolean; /** Explicit list of routes to prerender. */ @@ -863,7 +884,10 @@ export interface NitroOptions extends PresetOptions { * @see https://nitro.build/config#traceopts * @see https://github.com/unjs/nf3 */ - traceOpts?: Pick; + traceOpts?: Pick< + ExternalsTraceOptions, + "nft" | "traceAlias" | "chmod" | "transform" | "hooks" + >; // Advanced @@ -904,7 +928,9 @@ export interface NitroOptions extends PresetOptions { * Can be a shell command (`./` paths are resolved relative to the output directory) * or a function (used by presets that deploy programmatically). */ - deploy?: string | ((nitro: Nitro, opts: { args?: string[] }) => void | Promise); + deploy?: + | string + | ((nitro: Nitro, opts: { args?: string[] }) => void | Promise); }; /** @@ -1080,7 +1106,9 @@ type CustomDriverName = string & { _custom?: any }; * Driver options are inferred from the `driver` name. */ export type BuiltinStorageMount = { - [Name in BuiltinDriverName]: { driver: Name } & (Name extends keyof BuiltinDriverOptions + [Name in BuiltinDriverName]: { + driver: Name; + } & (Name extends keyof BuiltinDriverOptions ? SerializableOptions : unknown); }[BuiltinDriverName]; @@ -1127,7 +1155,10 @@ export type DatabaseConnectionConfig = { }[ConnectorName]; /** Map of {@link DatabaseConnectionName} to {@link DatabaseConnectionConfig}. */ -export type DatabaseConnectionConfigs = Record; +export type DatabaseConnectionConfigs = Record< + DatabaseConnectionName, + DatabaseConnectionConfig +>; // Runtime config diff --git a/src/types/global.ts b/src/types/global.ts index 3af8907827..dc8d4fd2cf 100644 --- a/src/types/global.ts +++ b/src/types/global.ts @@ -12,6 +12,7 @@ export interface NitroImportMeta { _asyncContext?: boolean; _tasks?: boolean; _websocket?: boolean; + _runtimeBaseURL?: boolean; } declare global { From 6f27549410226dd1a64aa8e681f233d7535be9f4 Mon Sep 17 00:00:00 2001 From: Jan Vollmer Date: Sat, 26 Sep 2026 11:28:33 +0200 Subject: [PATCH 2/4] revert formatting Signed-off-by: Jan Vollmer --- src/runtime/virtual/app.ts | 5 +---- src/types/config.ts | 40 ++++++++------------------------------ 2 files changed, 9 insertions(+), 36 deletions(-) diff --git a/src/runtime/virtual/app.ts b/src/runtime/virtual/app.ts index 1df746ddf8..a9dbbe1173 100644 --- a/src/runtime/virtual/app.ts +++ b/src/runtime/virtual/app.ts @@ -10,10 +10,7 @@ export function createNitroApp(): NitroApp { return errorHandler(error, event); }, }); - const captureError: NonNullable = ( - error, - errorCtx, - ) => { + const captureError: NonNullable = (error, errorCtx) => { if (errorCtx?.event) { const errors = errorCtx.event.req.context?.nitro?.errors; if (errors) { diff --git a/src/types/config.ts b/src/types/config.ts index 85cabb192d..da3f1b3fec 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -1,10 +1,5 @@ import type commonjs from "@rollup/plugin-commonjs"; -import type { - C12InputConfig, - ConfigWatcher, - DotenvOptions, - ResolvedConfig, -} from "c12"; +import type { C12InputConfig, ConfigWatcher, DotenvOptions, ResolvedConfig } from "c12"; import type { WatchConfigOptions } from "c12"; import type { ChokidarOptions } from "chokidar"; import type { CompatibilityDateSpec, CompatibilityDates } from "compatx"; @@ -12,11 +7,7 @@ import type { LogLevel } from "consola"; import type { ConnectorName, ConnectorOptions } from "db0"; import type { NestedHooks } from "hookable"; import type { ProxyServerOptions } from "httpxy"; -import type { - PresetName, - PresetNameInput, - PresetOptions, -} from "../presets/index.ts"; +import type { PresetName, PresetNameInput, PresetOptions } from "../presets/index.ts"; import type { TSConfig } from "pkg-types"; import type { Preset as UnenvPreset } from "unenv"; import type { BuiltinDriverName, BuiltinDriverOptions } from "unstorage"; @@ -40,9 +31,7 @@ import type { RollupConfig } from "./build.ts"; import type { NitroRouteConfig, NitroRouteRules } from "./route-rules.ts"; import type { JsonValue, SerializableOptions } from "./_utils.ts"; -type RollupCommonJSOptions = NonNullable< - Parameters[0] ->; +type RollupCommonJSOptions = NonNullable[0]>; /** * Fully resolved Nitro options available on `nitro.options`. @@ -659,10 +648,7 @@ export interface NitroOptions extends PresetOptions { * * @see https://nitro.build/config#routes */ - routes: Record< - string, - string | Omit - >; + routes: Record>; /** * Path(s) to custom runtime error handler(s). @@ -703,9 +689,7 @@ export interface NitroOptions extends PresetOptions { /** Fail the build when a route cannot be prerendered. */ failOnError?: boolean; /** Patterns (string, RegExp, or function) of routes to skip. */ - ignore?: Array< - string | RegExp | ((path: string) => undefined | null | boolean) - >; + ignore?: Array undefined | null | boolean)>; /** Skip prerendering assets without a base URL prefix. */ ignoreUnprefixedPublicAssets?: boolean; /** Explicit list of routes to prerender. */ @@ -884,10 +868,7 @@ export interface NitroOptions extends PresetOptions { * @see https://nitro.build/config#traceopts * @see https://github.com/unjs/nf3 */ - traceOpts?: Pick< - ExternalsTraceOptions, - "nft" | "traceAlias" | "chmod" | "transform" | "hooks" - >; + traceOpts?: Pick; // Advanced @@ -928,9 +909,7 @@ export interface NitroOptions extends PresetOptions { * Can be a shell command (`./` paths are resolved relative to the output directory) * or a function (used by presets that deploy programmatically). */ - deploy?: - | string - | ((nitro: Nitro, opts: { args?: string[] }) => void | Promise); + deploy?: string | ((nitro: Nitro, opts: { args?: string[] }) => void | Promise); }; /** @@ -1155,10 +1134,7 @@ export type DatabaseConnectionConfig = { }[ConnectorName]; /** Map of {@link DatabaseConnectionName} to {@link DatabaseConnectionConfig}. */ -export type DatabaseConnectionConfigs = Record< - DatabaseConnectionName, - DatabaseConnectionConfig ->; +export type DatabaseConnectionConfigs = Record; // Runtime config From f23a26e04e7c9f7df76248e4b35efba04ce9601a Mon Sep 17 00:00:00 2001 From: Jan Vollmer Date: Sat, 26 Sep 2026 11:53:35 +0200 Subject: [PATCH 3/4] revert more formatting Signed-off-by: Jan Vollmer --- src/types/config.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/types/config.ts b/src/types/config.ts index da3f1b3fec..83ba1443ce 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -1085,9 +1085,7 @@ type CustomDriverName = string & { _custom?: any }; * Driver options are inferred from the `driver` name. */ export type BuiltinStorageMount = { - [Name in BuiltinDriverName]: { - driver: Name; - } & (Name extends keyof BuiltinDriverOptions + [Name in BuiltinDriverName]: { driver: Name } & (Name extends keyof BuiltinDriverOptions ? SerializableOptions : unknown); }[BuiltinDriverName]; From faa2d31681d8c519f0dbf4196dc043f109083d67 Mon Sep 17 00:00:00 2001 From: Jan Vollmer Date: Sat, 26 Sep 2026 16:38:54 +0200 Subject: [PATCH 4/4] create an actually working example Signed-off-by: Jan Vollmer --- src/build/config.ts | 1 - src/build/virtual/app.ts | 13 +++++++++++++ src/runtime/virtual/app.ts | 6 +----- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/build/config.ts b/src/build/config.ts index 6565f05083..ca7d2c6457 100644 --- a/src/build/config.ts +++ b/src/build/config.ts @@ -24,7 +24,6 @@ export function baseBuildConfig(nitro: Nitro) { _asyncContext: nitro.options.experimental.asyncContext, _tasks: nitro.options.experimental.tasks, _websocket: nitro.options.features.websocket ?? nitro.options.experimental.websocket, - _runtimeBaseURL: nitro.options.features.runtimeBaseURL, }; const replacements = { diff --git a/src/build/virtual/app.ts b/src/build/virtual/app.ts index a1500ab6c1..55578eda3d 100644 --- a/src/build/virtual/app.ts +++ b/src/build/virtual/app.ts @@ -11,6 +11,7 @@ export default function app(nitro: Nitro) { const hasPlugins = nitro.options.plugins.length > 0; const hasHooks = nitro.options.features?.runtimeHooks ?? hasPlugins; const hasAsyncContext = !!nitro.options.experimental.asyncContext; + const runtimeBaseUrl = nitro.options.features.runtimeBaseURL; const routingImports = [ hasRoutes && "findRoute", @@ -90,6 +91,18 @@ export default function app(nitro: Nitro) { ` };` ); + if (runtimeBaseUrl) { + imports.push(`import { requestWithBaseURL } from "h3";`); + code.push( + ``, + ` const baseUrl = process.env.NITRO_APP_BASE_URL ?? "";`, + ` const baseHandler = appHandler;`, + ` appHandler = (req) => {`, + ` return baseHandler(requestWithBaseURL(req, baseUrl));`, + ` };` + ); + } + if (hasAsyncContext) { imports.push(`import { nitroAsyncContext } from "#nitro/runtime/context";`); code.push( diff --git a/src/runtime/virtual/app.ts b/src/runtime/virtual/app.ts index a9dbbe1173..a00f13b751 100644 --- a/src/runtime/virtual/app.ts +++ b/src/runtime/virtual/app.ts @@ -1,7 +1,7 @@ import "./_runtime_warn.ts"; import type { NitroApp } from "nitro/types"; import type { ServerRequest } from "srvx"; -import { H3Core, requestWithBaseURL } from "h3"; +import { H3Core } from "h3"; import errorHandler from "#nitro/virtual/error-handler"; export function createNitroApp(): NitroApp { @@ -22,10 +22,6 @@ export function createNitroApp(): NitroApp { fetch: (req: ServerRequest) => { req.context ||= {}; req.context.nitro = req.context.nitro || { errors: [] }; - if (import.meta._runtimeBaseURL) { - const baseURL = process.env.NITRO_APP_BASE_URL ?? ""; - req = requestWithBaseURL(req, baseURL); - } return h3App.fetch(req); }, h3: h3App,