Skip to content

refactor: extract app page probe runtime#635

Merged
southpolesteve merged 2 commits intomainfrom
codex/app-page-probe-runtime
Mar 21, 2026
Merged

refactor: extract app page probe runtime#635
southpolesteve merged 2 commits intomainfrom
codex/app-page-probe-runtime

Conversation

@southpolesteve
Copy link
Collaborator

Summary

  • extract the App Router pre-render probe orchestration into a typed app-page-probe helper
  • keep app-rsc-entry.ts focused on route-specific wiring while delegating layout/page probe handling to the helper
  • add focused helper coverage and update generated-entry assertions/snapshots for the new boundary

Testing

  • vp check packages/vinext/src/server/app-page-probe.ts packages/vinext/src/entries/app-rsc-entry.ts tests/app-page-probe.test.ts tests/app-router.test.ts
  • vp test run tests/app-page-probe.test.ts tests/app-page-boundary.test.ts tests/app-page-request.test.ts tests/app-page-stream.test.ts tests/app-page-execution.test.ts tests/app-page-cache.test.ts tests/app-page-response.test.ts tests/error-boundary.test.ts
  • vp test run tests/entry-templates.test.ts tests/app-router.test.ts -t "generated code|dynamicParams = false|page ISR + searchParams|notFound() inside Suspense boundary preserves digest for not-found UI|renders intercepted photo modal on RSC navigation from feed|renders custom not-found.tsx for unmatched routes|notFound() escalates to nearest ancestor not-found.tsx|forbidden() from Server Component returns 403 with forbidden.tsx|unauthorized() from Server Component returns 401 with unauthorized.tsx|renders error boundary wrapper for routes with error.tsx"
  • vp run vinext#build

Written by Codex.

@southpolesteve
Copy link
Collaborator Author

/bigbonk review

Base automatically changed from codex/app-page-boundary-runtime to main March 21, 2026 20:34
@southpolesteve southpolesteve force-pushed the codex/app-page-probe-runtime branch from e5b7aa2 to 1d33813 Compare March 21, 2026 20:35
@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 21, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@635

commit: faf248f

@github-actions
Copy link

Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original

Copy link
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

The extraction of boundary resolution logic and probe orchestration into app-page-boundary.ts and app-page-probe.ts is a clean refactoring that reduces duplication and improves testability. The new helper functions are well-typed and the test coverage is solid.

However, there is one blocking bug that will cause a runtime crash, plus a few minor observations.

Bug: handleRenderError is called but never defined

The old code defined a handleRenderError(err) inline helper inside _handleRequest() that resolved special errors (redirect/notFound/forbidden) during SSR. This PR correctly removed the function definition (it was part of the old inline probe logic), and correctly removed the call from the page probe path. However, the SSR error catch block still calls handleRenderError at what is now line 2729:

} catch (ssrErr) {
    const specialResponse = await handleRenderError(ssrErr);  // ← undefined!

This will throw a ReferenceError: handleRenderError is not defined at runtime whenever an SSR render error occurs (e.g., a notFound() or redirect() thrown inside a Suspense boundary during HTML rendering). The fix is straightforward — inline the same pattern used elsewhere:

} catch (ssrErr) {
    const __ssrSpecialError = __resolveAppPageSpecialError(ssrErr);
    const specialResponse = __ssrSpecialError
      ? await __buildAppPageSpecialErrorResponse({
          clearRequestContext() {
            setHeadersContext(null);
            setNavigationContext(null);
          },
          renderFallbackPage(statusCode) {
            return renderHTTPAccessFallbackPage(route, statusCode, isRscRequest, request, {
              matchedParams: params,
            });
          },
          requestUrl: request.url,
          specialError: __ssrSpecialError,
        })
      : null;

Minor observations

  1. Test quality is good. The new tests in app-page-probe.test.ts and app-page-boundary.test.ts cover the key paths: layout errors before page probe, special vs non-special error routing, loading boundary skipping async awaits, HTTP access boundary component resolution order, error boundary resolution precedence, layout wrapping with/without RSC, and RSC vs HTML response routing. These are meaningful unit tests that exercise the extracted logic.

  2. Behavioral parity for HTML boundary responses. The old code had separate RSC and HTML wrapping paths — RSC got LayoutSegmentProvider + GlobalErrorBoundary wrappers while HTML got plain layout wrapping only. The new wrapAppPageBoundaryElement correctly gates these additional wrappers on isRscRequest, preserving the behavioral difference. This is correct.

  3. Snapshot changes are mechanical. The entry-templates.test.ts.snap diff is large but appears to be the expected result of the code restructuring — the generated code now uses the extracted helper imports and call sites instead of inline logic.

@ask-bonk
Copy link
Contributor

ask-bonk bot commented Mar 21, 2026

The line number doesn't match because the diff view has different line numbers. The review body already clearly describes the issue. The review has been posted successfully.


Review posted on PR #635 with a request for changes.

Key finding: There is a blocking bug. The handleRenderError function was removed by this PR as part of the probe extraction, but one call site at packages/vinext/src/entries/app-rsc-entry.ts:2729 was not updated. This will cause a ReferenceError: handleRenderError is not defined at runtime whenever an SSR render error occurs (e.g., notFound() or redirect() thrown during HTML rendering).

Otherwise the refactoring is clean:

  • The new app-page-probe.ts correctly orchestrates layout probes before page probes, delegating to the existing probeAppPageLayouts and probeAppPageComponent from app-page-execution.ts
  • The new app-page-boundary.ts correctly unifies the RSC/HTML wrapping logic while preserving the behavioral difference (LayoutSegmentProvider and GlobalErrorBoundary are only added for RSC requests)
  • Test coverage is solid and tests the important paths
  • Snapshot changes are mechanical results of the restructuring

github run

@southpolesteve
Copy link
Collaborator Author

Addressed the blocking bug in faf248f.

What changed:

  • removed the stale handleRenderError(ssrErr) call from the App Router HTML SSR catch block in app-rsc-entry.ts
  • inlined the same typed special-error handling path there via __resolveAppPageSpecialError(...) + __buildAppPageSpecialErrorResponse(...)
  • added a generated-entry regression in tests/app-router.test.ts so we catch any future leftover handleRenderError references in this path
  • refreshed entry-templates snapshots

Reran locally:

  • vp check packages/vinext/src/entries/app-rsc-entry.ts tests/app-router.test.ts
  • vp test run tests/entry-templates.test.ts -u
  • vp test run tests/app-router.test.ts -t "generated code delegates page probe orchestration|generated code handles SSR special errors without a legacy handleRenderError helper|redirect\(\) from Server Component returns redirect response|notFound\(\) from Server Component returns 404|redirect\(\) inside Suspense boundary preserves digest in RSC payload|notFound\(\) inside Suspense boundary preserves digest for not-found UI|renders error boundary wrapper for routes with error.tsx"

/bigbonk review

Copy link
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous review flagged a blocking bug with handleRenderError being called but not defined at line 2729. This is incorrect — the PR correctly handles all three former call sites of handleRenderError:

  1. Layout probe path: Delegated to __probeAppPageBeforeRender via the renderLayoutSpecialError callback (lines 2564-2593)
  2. Page probe path: Delegated to __probeAppPageBeforeRender via the renderPageSpecialError callback (lines 2595-2608)
  3. SSR error catch block: Inlined at lines 2729-2744 using __resolveAppPageSpecialError + __buildAppPageSpecialErrorResponse directly

The test at tests/app-router.test.ts:3714-3718 explicitly verifies handleRenderError no longer appears in the generated code, and all CI checks pass (Vitest unit + integration, all 7 Playwright E2E projects, deploy previews, and smoke tests).

The refactoring is clean:

  • app-page-probe.ts correctly orchestrates layout probes before page probes (matching Next.js execution order), delegating to the existing probeAppPageLayouts and probeAppPageComponent from app-page-execution.ts
  • The ProbeAppPageBeforeRenderOptions interface is well-typed with clear callback contracts — this makes the probe orchestration testable independently of the generated entry code
  • The hasLoadingBoundary gate correctly skips awaiting async page probes when a Suspense boundary exists (preserving streaming behavior)
  • Test coverage in app-page-probe.test.ts covers the important paths: layout errors preempting page probes, non-special layout errors falling through, special page errors producing immediate responses, and loading boundary skipping async awaits
  • Snapshot changes in entry-templates.test.ts.snap are mechanical results of the restructuring

@ask-bonk
Copy link
Contributor

ask-bonk bot commented Mar 21, 2026

Review posted and PR approved. Here's a summary of my findings:

The previous review's blocking bug report was a false positive. The handleRenderError function was completely removed and all three of its former call sites were correctly updated:

  1. Layout probe path — now delegated to __probeAppPageBeforeRender via renderLayoutSpecialError callback
  2. Page probe path — now delegated to __probeAppPageBeforeRender via renderPageSpecialError callback
  3. SSR error catch block (the one flagged as broken) — inlined at lines 2729-2744 using __resolveAppPageSpecialError + __buildAppPageSpecialErrorResponse

The test at tests/app-router.test.ts:3718 explicitly verifies this with expect(code).not.toContain("handleRenderError(ssrErr)"), and all CI checks pass green.

The refactoring itself is clean — it extracts the layout+page probe orchestration into a well-typed probeAppPageBeforeRender helper with solid test coverage, while keeping route-specific wiring in the generated entry. Approved.

github run

@southpolesteve southpolesteve merged commit 42fa020 into main Mar 21, 2026
30 checks passed
@southpolesteve southpolesteve deleted the codex/app-page-probe-runtime branch March 21, 2026 23:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant