fix: report stale ISR background revalidation errors via onRequestError#947
Conversation
…or (cloudflare#775) Next.js 16 now reports errors during background ISR revalidation of stale pages via routeModule.onRequestError(). Previously these errors were silently swallowed (only logged to console). This ensures instrumentation hooks (Sentry, OpenTelemetry) observe revalidation failures. - Add errorContext parameter to triggerBackgroundRegeneration() in isr-cache.ts - Report errors via reportRequestError() with revalidateReason: "stale" - Update App Router entry template to pass route context to regen callbacks - Update Pages Router entry template to forward error context through wrappers - Update dev-server.ts and pages-page-data.ts to pass route context - Update snapshots and test assertions for generated code changes
commit: |
|
/bigbonk review |
There was a problem hiding this comment.
Review Summary
Good feature — surfacing ISR background revalidation errors to onRequestError instrumentation hooks is the right thing to do, and the approach is sound. The implementation correctly covers all four paths (App Router route handlers, App Router pages, Pages Router dev, Pages Router prod via pages-page-data.ts). Test assertions are updated appropriately.
A few items to address:
Issues
-
Missing
voidon_reportRequestErrorin the App Router generated entry — see inline comment. -
JSDoc removed from
triggerBackgroundRegeneration— see inline comment. -
No unit test for the new error-reporting behavior. The existing
isr-cache.test.tstest "handles render function errors gracefully" (line 222) verifies that errors are caught andconsole.erroris called, but there's no test verifying thatreportRequestErroris called whenerrorContextis provided. This is the core new behavior and should have dedicated test cases:- When
errorContextis provided and renderFn throws,reportRequestErroris called with the correct args (revalidateReason: "stale", correctrouterKind, etc.) - When
errorContextis omitted and renderFn throws,reportRequestErroris NOT called - When the error is not an
Errorinstance (e.g., a string), it gets wrapped innew Error(String(err))
These can mock by setting
globalThis.__VINEXT_onRequestErrorHandler__directly in the test. - When
-
PR body mentions
ISRRegenErrorContexttype but no such named type exists in the code. The error context is defined inline. Minor doc inconsistency.
Nits
- The
dev-server.tsdiff is +115/-103 but the actual logic change is just adding the third argument totriggerBackgroundRegeneration. The reformatting noise makes the diff harder to review.
| .catch((err) => { | ||
| console.error("[vinext] ISR regen failed for " + key + ":", err); | ||
| if (errorContext) { | ||
| _reportRequestError( |
There was a problem hiding this comment.
The _reportRequestError promise is not void-ed here, unlike the equivalent call in isr-cache.ts (line 94) which uses void reportRequestError(...). For consistency and to signal fire-and-forget intent:
| _reportRequestError( | |
| void _reportRequestError( |
| * alive until the regeneration completes even after the Response is returned. | ||
| */ | ||
| export function triggerBackgroundRegeneration(key: string, renderFn: () => Promise<void>): void { | ||
| export function triggerBackgroundRegeneration( |
There was a problem hiding this comment.
The JSDoc that was here before explained the dedup semantics, waitUntil registration, and the no-op-on-duplicate-key behavior. That's useful context for callers. Please restore and update it to document the new errorContext parameter. Something like:
/**
* Trigger a background regeneration for a cache key.
*
* If a regeneration for this key is already in progress, this is a no-op.
* The renderFn should produce the new cache value and call isrSet internally.
*
* On Cloudflare Workers the regeneration promise is registered with
* `ctx.waitUntil()` via the ALS-backed ExecutionContext, keeping the isolate
* alive until the regeneration completes even after the Response is returned.
*
* When `errorContext` is provided and the render function fails, the error
* is reported via `reportRequestError` (instrumentation hook) with
* `revalidateReason: "stale"`.
*/
|
Review posted successfully. Here's a summary of the review: Review for PR #947 — Report stale ISR background revalidation errors via onRequestError The approach is sound — all four ISR background regen paths (App Router routes, App Router pages, Pages Router dev, Pages Router prod) now forward errors to the
|
Resolves merge conflicts in generated entry templates and snapshots. Addresses code review feedback: - void _reportRequestError in generated entry for fire-and-forget intent - Restore JSDoc on triggerBackgroundRegeneration with errorContext docs - Add unit tests for reportRequestError on ISR background regen failures
|
/bigbonk review |
There was a problem hiding this comment.
Review Summary
This is a clean, well-structured change that addresses a real gap — ISR background revalidation errors were silently swallowed (only console.error), now they're reported through the onRequestError instrumentation hook so Sentry/OTel/etc. can pick them up.
What's good
- All four ISR regen paths are covered: App Router route handlers, App Router pages, Pages Router dev (
dev-server.ts), Pages Router prod (pages-page-data.ts→pages-server-entry.ts). No path left behind. - The shared
isr-cache.tsimplementation is the source of truth for the typed runtime. The generated App Router entry duplicates the logic inline, but that's justified by the RSC environment import constraints (documented atapp-rsc-entry.ts:510-513). - Tests cover the three key cases: error reported with context, no report without context, non-Error values wrapped. These test the actual
reportRequestErrorpathway viaglobalThis.__VINEXT_onRequestErrorHandler__. request.pathuses the cache key rather than the original URL — this is the right call since the original request context is gone by the time background regen runs. The cache key contains the pathname, which is the most useful identifier.- Previous review feedback was already addressed:
voidon_reportRequestErroris present, JSDoc was preserved and updated with the newerrorContextparameter docs.
Minor notes
- The
dev-server.tsdiff is +115/-103 but the logic change is just adding the third argument totriggerBackgroundRegeneration. The rest is reformatting from the function call restructuring. I verified the logic is identical. - The
pages-page-data.tstype narrowing (routerKind: "Pages Router") is appropriately specific for that module.
Looks ready to merge.
| if (errorContext) { | ||
| void reportRequestError( | ||
| err instanceof Error ? err : new Error(String(err)), | ||
| { path: key, method: "GET", headers: {} }, |
There was a problem hiding this comment.
Nit: The { path: key, method: "GET", headers: {} } synthetic request object is the right call here (the original request is long gone by the time background regen runs). Worth knowing that path will be something like app:buildid:/blog/hello:html rather than /blog/hello — this is fine for instrumentation since it uniquely identifies what failed, but if users report confusion with these cache-key-style paths in Sentry, a future improvement could extract just the pathname portion.
|
Review posted successfully. Here's a summary: Approved PR #947 — Report stale ISR background revalidation errors via The change is clean and complete. All four ISR background regeneration paths (App Router routes, App Router pages, Pages Router dev, Pages Router prod) now forward errors to the |
Fixes #775
Summary
onRequestErrorinstrumentation hook (Sentry, OpenTelemetry, etc.) instead of being silently swallowedtriggerBackgroundRegeneration()inisr-cache.tswith optional error context parameterrevalidateReason: "stale", matching Next.js 16 behavior (Ensure app-page reports stale ISR revalidation errors via onRequestError vercel/next.js#92282)Changes
server/isr-cache.ts: AddedISRRegenErrorContexttype and error reporting in background regen catchentries/app-rsc-entry.ts: Updated inline__triggerBackgroundRegenerationto call_reportRequestErrorentries/pages-server-entry.ts: Forward error context through wrapper functionserver/dev-server.ts: Pass Pages Router route context to regen callsserver/pages-page-data.ts: Pass route context for Pages Router page data regenTest plan