From 3bed9608712eec85b7c2442879cc1c9142d666be Mon Sep 17 00:00:00 2001 From: kricsleo Date: Sun, 26 Oct 2025 20:38:44 +0800 Subject: [PATCH 1/3] refactor(prerender): show details for 5xx handled errors --- .../_nitro/runtime/nitro-prerenderer.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/presets/_nitro/runtime/nitro-prerenderer.ts b/src/presets/_nitro/runtime/nitro-prerenderer.ts index d49a222bd3..42763407d4 100644 --- a/src/presets/_nitro/runtime/nitro-prerenderer.ts +++ b/src/presets/_nitro/runtime/nitro-prerenderer.ts @@ -1,9 +1,28 @@ import "#nitro-internal-pollyfills"; +import consola from "consola"; +import { getRequestHeader, getRequestURL, H3Error, isEvent } from "h3"; import { useNitroApp } from "nitropack/runtime"; import { trapUnhandledNodeErrors } from "nitropack/runtime/internal"; const nitroApp = useNitroApp(); +nitroApp.hooks.hook("error", (error, context) => { + if ( + isEvent(context.event) && + !(error as H3Error).unhandled && + (error as H3Error).statusCode >= 500 && + getRequestHeader(context.event, "x-nitro-prerender") + ) { + const url = getRequestURL(context.event).href; + consola.error( + `[prerender error]`, + `[${context.event.method}]`, + `[${url}]`, + error + ); + } +}); + export const localFetch = nitroApp.localFetch; export const closePrerenderer = () => nitroApp.hooks.callHook("close"); From 8477332e759fb67c4817f02565fec4bf07ae55f0 Mon Sep 17 00:00:00 2001 From: kricsleo Date: Sun, 26 Oct 2025 21:21:27 +0800 Subject: [PATCH 2/3] test: add tests --- test/fixture/nitro.config.ts | 1 + test/fixture/routes/prerender-error.ts | 3 +++ test/presets/vercel.test.ts | 5 +++++ test/tests.ts | 12 +++++++++++- 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 test/fixture/routes/prerender-error.ts diff --git a/test/fixture/nitro.config.ts b/test/fixture/nitro.config.ts index dcb2e233b8..564b390fa3 100644 --- a/test/fixture/nitro.config.ts +++ b/test/fixture/nitro.config.ts @@ -85,6 +85,7 @@ export default defineNitroConfig({ routeRules: { "/api/param/prerender4": { prerender: true }, "/api/param/prerender2": { prerender: false }, + "/prerender-error": { prerender: true }, "/rules/headers": { headers: { "cache-control": "s-maxage=60" } }, "/rules/cors": { cors: true, diff --git a/test/fixture/routes/prerender-error.ts b/test/fixture/routes/prerender-error.ts new file mode 100644 index 0000000000..d0265429b3 --- /dev/null +++ b/test/fixture/routes/prerender-error.ts @@ -0,0 +1,3 @@ +export default defineEventHandler(() => { + return new Error("Prerender error test"); +}); diff --git a/test/presets/vercel.test.ts b/test/presets/vercel.test.ts index e9ac00f04c..bf664c82c5 100644 --- a/test/presets/vercel.test.ts +++ b/test/presets/vercel.test.ts @@ -181,6 +181,10 @@ describe("nitro:preset:vercel", async () => { "dest": "/raw", "src": "/raw", }, + { + "dest": "/prerender-error", + "src": "/prerender-error", + }, { "dest": "/prerender-custom.html", "src": "/prerender-custom.html", @@ -562,6 +566,7 @@ describe("nitro:preset:vercel", async () => { "functions/modules.func (symlink)", "functions/node-compat.func (symlink)", "functions/prerender-custom.html.func (symlink)", + "functions/prerender-error.func (symlink)", "functions/prerender.func (symlink)", "functions/raw.func (symlink)", "functions/replace.func (symlink)", diff --git a/test/tests.ts b/test/tests.ts index dc1f8be75f..da846ad9f5 100644 --- a/test/tests.ts +++ b/test/tests.ts @@ -19,7 +19,8 @@ import { type FetchOptions, fetch } from "ofetch"; import { join, resolve } from "pathe"; import { isWindows, nodeMajorVersion } from "std-env"; import { joinURL } from "ufo"; -import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import consola from "consola"; +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; export interface Context { preset: string; @@ -36,6 +37,7 @@ export interface Context { env: Record; lambdaV1?: boolean; // [key: string]: unknown; + consolaError: ReturnType; } // https://github.com/nitrojs/nitro/pull/1240 @@ -109,6 +111,7 @@ export async function setupTest( redirect: "manual", ...(opts as any), }), + consolaError: vi.spyOn(consola, "error").mockImplementation(() => {}), }; // Set environment variables for process compatible presets @@ -157,6 +160,7 @@ export async function setupTest( } afterAll(async () => { + ctx.consolaError.mockRestore(); if (ctx.server) { await ctx.server.close(); } @@ -459,6 +463,12 @@ export function testNitro( expect(data).toBe("prerender4"); expect(headers["content-type"]).toBe("text/plain; charset=utf-16"); }); + + it("show details for 5xx handled errors", async () => { + expect(ctx.consolaError.mock.calls.flat().join(" ")).toContain( + "Prerender error test" + ); + }); } it("shows 404 for /build/non-file", async () => { From 6ccd430fae98723f1802061adb396009908c3579 Mon Sep 17 00:00:00 2001 From: kricsleo Date: Sun, 26 Oct 2025 21:32:00 +0800 Subject: [PATCH 3/3] test: fix tests --- test/tests.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/tests.ts b/test/tests.ts index da846ad9f5..ae2fa4bc28 100644 --- a/test/tests.ts +++ b/test/tests.ts @@ -21,6 +21,7 @@ import { isWindows, nodeMajorVersion } from "std-env"; import { joinURL } from "ufo"; import consola from "consola"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import type { MockInstance } from "vitest"; export interface Context { preset: string; @@ -37,7 +38,7 @@ export interface Context { env: Record; lambdaV1?: boolean; // [key: string]: unknown; - consolaError: ReturnType; + consolaError: MockInstance; } // https://github.com/nitrojs/nitro/pull/1240