diff --git a/src/core/config/resolvers/error.ts b/src/core/config/resolvers/error.ts index 3ea54ff672..346903b139 100644 --- a/src/core/config/resolvers/error.ts +++ b/src/core/config/resolvers/error.ts @@ -4,9 +4,13 @@ import { join } from "pathe"; export async function resolveErrorOptions(options: NitroOptions) { if (!options.errorHandler) { - options.errorHandler = join( - runtimeDir, - `internal/error/${options.dev ? "dev" : "prod"}` - ); + options.errorHandler = []; + } else if (!Array.isArray(options.errorHandler)) { + options.errorHandler = [options.errorHandler]; } + + // Always add the default error handler as the last one + options.errorHandler.push( + join(runtimeDir, `internal/error/${options.dev ? "dev" : "prod"}`) + ); } diff --git a/src/rollup/config.ts b/src/rollup/config.ts index 853fdd2202..c51dc28bf0 100644 --- a/src/rollup/config.ts +++ b/src/rollup/config.ts @@ -41,6 +41,7 @@ import { sourcemapMininify } from "./plugins/sourcemap-min"; import { storage } from "./plugins/storage"; import { timing } from "./plugins/timing"; import { virtual } from "./plugins/virtual"; +import { errorHandler } from "./plugins/error-handler"; import { resolveAliases } from "./utils"; export const getRollupConfig = (nitro: Nitro): RollupConfig => { @@ -344,6 +345,9 @@ export const getRollupConfig = (nitro: Nitro): RollupConfig => { rollupConfig.plugins.push(handlersMeta(nitro)); } + // Error handler + rollupConfig.plugins.push(errorHandler(nitro)); + // Polyfill rollupConfig.plugins.push( virtual( @@ -393,7 +397,6 @@ export const plugins = [ alias({ entries: resolveAliases({ "#build": buildDir, - "#nitro-internal-virtual/error-handler": nitro.options.errorHandler, "#internal/nitro": runtimeDir, "nitro/runtime": runtimeDir, "nitropack/runtime": runtimeDir, diff --git a/src/rollup/plugins/error-handler.ts b/src/rollup/plugins/error-handler.ts new file mode 100644 index 0000000000..15a74e6fe3 --- /dev/null +++ b/src/rollup/plugins/error-handler.ts @@ -0,0 +1,36 @@ +import type { Nitro } from "nitropack/types"; +import { virtual } from "./virtual"; + +export function errorHandler(nitro: Nitro) { + return virtual( + { + "#nitro-internal-virtual/error-handler": () => { + const errorHandlers = Array.isArray(nitro.options.errorHandler) + ? nitro.options.errorHandler + : [nitro.options.errorHandler]; + + return /* js */ ` +${errorHandlers.map((h, i) => `import errorHandler$${i} from "${h}";`).join("\n")} + +const errorHandlers = [${errorHandlers.map((_, i) => `errorHandler$${i}`).join(", ")}]; + +export default async function(error, event) { + for (const handler of errorHandlers) { + try { + await handler(error, event); + if (event.handled) { + return; // Response handled + } + } catch(error) { + // Handler itself thrown, log and continue + console.error(error); + } + } + // H3 will handle fallback +} +`; + }, + }, + nitro.vfs + ); +} diff --git a/src/types/config.ts b/src/types/config.ts index 2f05620180..d0d85b2239 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -181,7 +181,7 @@ export interface NitroOptions extends PresetOptions { handlers: NitroEventHandler[]; routeRules: { [path: string]: NitroRouteRules }; devHandlers: NitroDevEventHandler[]; - errorHandler: string; + errorHandler: string | string[]; devErrorHandler: NitroErrorHandler; prerender: { /** diff --git a/test/fixture/error.ts b/test/fixture/error.ts new file mode 100644 index 0000000000..b57d486090 --- /dev/null +++ b/test/fixture/error.ts @@ -0,0 +1,5 @@ +export default defineNitroErrorHandler((error, event) => { + if (event.path.includes("?custom_error_handler")) { + return send(event, "custom_error_handler"); + } +}); diff --git a/test/fixture/nitro.config.ts b/test/fixture/nitro.config.ts index 94c64f63fb..c97856c2f1 100644 --- a/test/fixture/nitro.config.ts +++ b/test/fixture/nitro.config.ts @@ -81,6 +81,7 @@ export default defineNitroConfig({ "db:migrate": { description: "Migrate database" }, "db:seed": { description: "Seed database" }, }, + errorHandler: "~/error.ts", routeRules: { "/api/param/prerender4": { prerender: true }, "/api/param/prerender2": { prerender: false }, diff --git a/test/tests.ts b/test/tests.ts index 7fe4dae579..8967e26140 100644 --- a/test/tests.ts +++ b/test/tests.ts @@ -394,6 +394,12 @@ export function testNitro( "x-content-type-options": "nosniff", "x-frame-options": "DENY", }); + + const { data } = await callHandler({ + url: "/api/error?custom_error_handler", + }); + expect(status).toBe(503); + expect(data).toBe("custom_error_handler"); }); it.skipIf(isWindows && ctx.preset === "nitro-dev")(