mirror of
https://github.com/cloudflare/vinext.git
synced 2026-05-09 08:25:34 +02:00
[PR #404] perf: eliminate page probe double-execution of page components #548
Labels
No labels
enhancement
enhancement
good first issue
help wanted
nextjs-tracking
nextjs-tracking
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/vinext#548
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/cloudflare/vinext/pull/404
Author: @james-elicx
Created: 3/10/2026
Status: 🔄 Open
Base:
main← Head:perf/eliminate-page-probe-double-execution📝 Commits (1)
a51efe3perf: eliminate page probe double-execution of page components📊 Changes
2 files changed (+616 additions, -294 deletions)
View changed files
📝
packages/vinext/src/entries/app-rsc-entry.ts(+88 -42)📝
tests/__snapshots__/entry-templates.test.ts.snap(+528 -252)📄 Description
The RSC entry had a "page probe" that called PageComponent() outside React's render cycle before every RSC render, solely to detect redirect()/notFound()/forbidden()/unauthorized() throws. The probe's result was discarded — only thrown errors mattered. Then the actual RSC render called the same component again, executing all data fetching and computation a second time.
For async server components (the common case — pages that fetch data), this meant every fetch, database query, and API call ran twice per request. On Cloudflare Workers, where there's no persistent process cache to deduplicate fetches, this doubled CPU time for data-heavy pages.
Investigation of Next.js source confirmed they do NOT use a probe. They handle redirect/notFound entirely during rendering: errors inside Suspense boundaries are caught by React error boundaries (rendered in-stream with status 200), while errors that escape all boundaries are caught by an outer try/catch before any bytes are flushed.
Replace the page probe with three strategies based on page type:
Routes WITH loading.tsx (Suspense): stream the RSC response directly. Errors inside the Suspense boundary flow through the RSC stream as error references for client-side handling, matching Next.js behavior.
SYNC pages (no loading.tsx): a lightweight sync-only probe catches redirect/notFound thrown synchronously by the page function. This is practically free — sync server components don't do data fetching. Errors from async children inside inline boundaries flow through the stream as before.
ASYNC pages (no loading.tsx): buffer the entire RSC stream, then check onError for redirect/notFound. Components execute only once during the actual RSC render. Latency characteristics are identical because without Suspense, nothing can stream until all async work completes anyway.
The layout probe is preserved unchanged — it's needed for correct parent-boundary selection when a layout throws notFound() (knowing which layout threw determines which not-found.tsx boundary to render).
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.