Skip to content

fix: pass thenable params and searchParams to probePage()#763

Merged
james-elicx merged 1 commit intocloudflare:mainfrom
NathanDrake2406:fix/probe-page-thenable-params
Apr 3, 2026
Merged

fix: pass thenable params and searchParams to probePage()#763
james-elicx merged 1 commit intocloudflare:mainfrom
NathanDrake2406:fix/probe-page-thenable-params

Conversation

@NathanDrake2406
Copy link
Copy Markdown
Contributor

Summary

  • probePage() passed raw null-prototype params instead of thenable params, causing TypeError for pages using await params (Next.js 15+ pattern)
  • The probe also omitted searchParams, causing pages using await searchParams to fail
  • Both issues silently defeated the probe's purpose (early notFound()/redirect() detection)
  • The layout probe already used thenable params correctly — only the page probe was inconsistent

The fix computes _asyncSearchParams (URLSearchParams converted to a plain object then wrapped in makeThenableParams()) alongside _asyncLayoutParams, and passes both to the probePage() callback — matching the same prop shape that buildPageElement() gives to the real render.

Test plan

  • Unit: probe detects notFound() from an async-params page when params are thenable
  • Unit: probe detects redirect() from an async-searchParams page when searchParams are thenable
  • Unit: probe silently fails when searchParams is omitted (documents the bug)
  • Integration: page using await params then notFound() returns 404
  • Integration: page using await searchParams then redirect() returns 307
  • Integration: pages render normally with valid params/searchParams
  • Entry template snapshots updated
  • tests/features.test.ts — all 264 tests pass

probePage() passed raw null-prototype params instead of thenable params,
causing TypeError for any page using the Next.js 15+ async params pattern
(await params). The probe also omitted searchParams entirely. Both issues
caused the probe to silently fail, defeating its purpose of early notFound()
and redirect() detection. The layout probe already used the correct thenable
params — the page probe was the only inconsistent path.
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 3, 2026

Open in StackBlitz

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

commit: 4816ece

@james-elicx
Copy link
Copy Markdown
Collaborator

/bigbonk review

Copy link
Copy Markdown
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.

Clean, well-tested fix for a real bug. The core change is small and correct: probePage() now receives the same thenable params/searchParams shape that buildPageElement() gives to the real render, so async-params pages no longer throw TypeError during probe.

The test coverage is thorough — unit tests for the probe logic, an explicit regression test documenting the old broken behavior, and integration tests with fixture pages exercising the full dev-server path. The searchParams conversion logic correctly mirrors the spObj construction in buildPageElement() (lines 967-978).

One minor observation below, but nothing blocking.

Comment on lines +2389 to +2397
const _probeSearchObj = {};
url.searchParams.forEach(function(v, k) {
if (k in _probeSearchObj) {
_probeSearchObj[k] = Array.isArray(_probeSearchObj[k]) ? _probeSearchObj[k].concat(v) : [_probeSearchObj[k], v];
} else {
_probeSearchObj[k] = v;
}
});
const _asyncSearchParams = makeThenableParams(_probeSearchObj);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: this URLSearchParams-to-plain-object conversion is duplicated from buildPageElement() (lines 967-978). They're identical except for the hasSearchParams tracking. If you wanted to reduce the duplication you could extract a small searchParamsToObject(urlSearchParams) helper at the top of the generated entry (next to makeThenableParams), but given this is codegen and the AGENTS.md guidance to keep entries thin, I wouldn't block on it — just flagging for awareness in case this conversion shows up a third time.

Comment on lines +231 to +273
it("probe silently fails when searchParams is omitted and page awaits it", async () => {
const REDIRECT_ERROR = new Error("NEXT_REDIRECT");

// When the old probePage() omitted searchParams, the component received
// undefined for that prop. `await undefined` produces undefined, then
// destructuring undefined throws TypeError. The probe catches it but
// doesn't recognize it as a special error, so it returns null.
const renderPageSpecialError = vi.fn(async () => new Response(null, { status: 307 }));

const response = await probeAppPageBeforeRender({
hasLoadingBoundary: false,
layoutCount: 0,
probeLayoutAt() {
return null;
},
probePage() {
// Simulate what happens at runtime when searchParams is not passed:
// the page component receives no searchParams prop, then tries to
// destructure it after await. This throws TypeError.
return Promise.resolve().then(() => {
throw new TypeError("Cannot destructure property 'dest' of undefined");
});
},
renderLayoutSpecialError() {
throw new Error("unreachable");
},
renderPageSpecialError,
resolveSpecialError(error) {
return error === REDIRECT_ERROR
? { kind: "redirect", location: "/about", statusCode: 307 }
: null;
},
runWithSuppressedHookWarning(probe) {
return probe();
},
});

// The probe catches the TypeError but resolveSpecialError returns null
// for it (TypeError is not a special error) so the probe returns null.
// The redirect is never detected early.
expect(response).toBeNull();
expect(renderPageSpecialError).not.toHaveBeenCalled();
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice — explicitly documenting the broken behavior as a test case is a good pattern. This makes it clear what the old code path did and protects against regressions if the probe's error handling ever changes.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk bot commented Apr 3, 2026

Review submitted. Here's a summary:

Approved — this is a clean, well-scoped fix for a real bug.

What the PR fixes: probePage() in the generated RSC entry was passing raw null-prototype params (from trie match) and omitting searchParams entirely. Pages using the Next.js 15+ await params / await searchParams pattern threw TypeError during probe, which the probe caught but didn't recognize as a special error — silently defeating early notFound()/redirect() detection.

What the fix does: Computes _asyncSearchParams (URLSearchParams converted to a plain object, then wrapped with makeThenableParams()) alongside _asyncLayoutParams, and passes both to probePage(). This matches the same prop shape that buildPageElement() gives to the real render.

One minor note: The URLSearchParams-to-plain-object conversion is now duplicated between the probe setup and buildPageElement(). Not blocking, but worth extracting into a shared helper if it appears a third time.

github run

@james-elicx james-elicx merged commit 301b8cb into cloudflare:main Apr 3, 2026
26 checks passed
@NathanDrake2406 NathanDrake2406 deleted the fix/probe-page-thenable-params branch April 3, 2026 11:46
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.

2 participants