Skip to content

fix(app-router): build client page searchParams in SSR and the browser - #3457

Draft
james-elicx wants to merge 22 commits into
isr-query/05-candidate-ssr-gatesfrom
isr-query/07-client-page-search-params
Draft

james-elicx wants to merge 22 commits into
isr-query/05-candidate-ssr-gatesfrom
isr-query/07-client-page-search-params

Conversation

@james-elicx

Copy link
Copy Markdown
Member

Stacked on #3455. Plan PR 2: client page searchParams are built on the SSR and browser side, like Next.js's ClientPageRoot (client/components/client-page.tsx).

Problem

A client page got searchParams through Flight. Flight called then on the prop while serializing it, so:

  • every client page counted as reading the query, even when it never did;
  • its RSC payload carried the query.

A static client page that never touches searchParams was therefore never provably query-free. #3456's safety net would stop storing it. A client page that does read searchParams with no query in the URL was stored, which Next.js never does.

Change

  • RSC side (server/app-page-element-builder.ts): when the request has searchParams and the page is a client reference, the builder renders ClientPageRoot with { Component, pageProps }. searchParams isn't in those props, so Flight never touches it and the RSC payload is query-free. Slot client pages drop the route's searchParams from their props. Boundary renders with no searchParams render the client page directly, as before.
  • shims/client-page-root.tsx ("use client"):
    • SSR: it reads a per-render thenable from the navigation context. handleSsr builds this with makeClientPageSsrSearchParamsThenable. Any read, including a synchronous property read, goes through the same observer as a server page's: it marks the render dynamic, records the searchParams usage, and throws for dynamic = "error". So a client page that reads the query is never stored, and one that doesn't stays cacheable. force-static and PPR fallback shells get an untracked thenable instead, so their output doesn't change.
    • Browser: it builds an already-settled promise from useSearchParams(). The query keys are copied onto it for synchronous reads, except the names Promise and React rely on.
  • Class component pages: they now follow function components. The markDynamic: hasRequestSearchParams exemption was only there because Flight always awaited client page props (fbf68d5). Class components were caught by it only because isReactOwnedAppComponent groups them with client references.
  • Shared helpers:
    • collectAppPageSearchParams now uses searchParamsToRecord (utils/query.ts).
    • The reserved thenable keys moved into shims/internal/thenable-well-known-properties.ts, so the browser shim can use them.

Tests

  • tests/client-page-root.test.ts, with the real headers module:
    • a read opens the useSearchParams() gate and latches dynamic;
    • a synchronous read returns the real value and is dynamic;
    • a page that doesn't read stays static;
    • the untracked path works;
    • the promise is stable across renders;
    • the browser promise builder works.
  • tests/app-page-element-builder.test.ts:
    • a client page's ClientPageRoot props have no searchParams, the query doesn't appear, and nothing is observed;
    • the same holds for a slot client page;
    • a class page that reads with no query is dynamic.
  • tests/app-page-render.test.ts: a candidate client page that doesn't read is written to the cache; one that reads with use() in SSR isn't.
  • Response Store e2e (examples/response-store-demo, new /client-search-params/* routes):
    • a client page that doesn't read, requested with a query, goes MISS then HIT, and neither its HTML nor its RSC payload contains the query;
    • a client page that reads is never a HIT, with or without a query, renders the real query, and stores nothing.
  • tests/e2e/app-router-prod/static-hydration.spec.ts: ?q=hello hydrates, a Link to ?q=world updates the value, and there are no console errors.

Known differences and residuals

  • RSC-only requests run no SSR, so a read that only happens in SSR isn't seen there, and the query-free RSC entry can still be stored (plan row 7a).
  • Rewrites: in the browser the prop follows the URL's query, not the rewritten one, which matches vinext's existing useSearchParams() hydration. SSR uses the effective query.
  • force-static and browser-only reads: the browser prop shows the URL's query. Next.js gives {} from its static payload.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Please review exact head 7b4fee6f124a834d82b5cc4ae97b2807c37f61de without modifying or pushing the branch. Report all actionable findings within your 8-minute time limit.

@pkg-pr-new

pkg-pr-new Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@vinext/cloudflare

npm i https://pkg.pr.new/@vinext/cloudflare@3457

create-vinext-app

npm i https://pkg.pr.new/create-vinext-app@3457

@vinext/types

npm i https://pkg.pr.new/@vinext/types@3457

vinext

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

@cloudflare/workers-response-store

npm i https://pkg.pr.new/@cloudflare/workers-response-store@3457

commit: 4b18cd0

@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared 4b18cd0 against base d73cba8 using alternating same-runner rounds. Next.js was unchanged and skipped.

0 improved · 1 regressed · 5 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 142.5 KB 143.4 KB ⚫ +0.6%
Client entry size (gzip) vinext 129.9 KB 131.5 KB ⚫ +1.2%
Dev server cold start vinext 2.68 s 2.74 s 🔴 +2.1%
Production build time vinext 2.90 s 2.93 s ⚫ +0.8%
RSC entry closure size (gzip) vinext 132.0 KB 132.2 KB ⚫ +0.1%
Server bundle size (gzip) vinext 230.9 KB 233.2 KB ⚫ +1.0%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

): Promise<ClientPageSearchParams> {
const record = searchParamsToRecord(searchParams);
const promise = Promise.resolve(record) as SettledClientPageSearchParams;
promise.status = "fulfilled";

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.

[P2] Keep React bookkeeping out of synchronous enumeration. These assignments create enumerable own status and value properties, while the SSR makeThenableParams proxy filters those reserved keys from ownKeys. A client page that synchronously does Object.keys(searchParams) or { ...searchParams } therefore renders only query keys during SSR but sees status/value in the browser, causing a hydration mismatch and exposing implementation fields. Define these bookkeeping properties as non-enumerable (and add an enumeration/hydration test).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 6eb65c6. React's status and value on the browser promise are now non-enumerable, so Object.keys, Object.entries and spreading list the same query keys as the SSR thenable. The promise also resolves to a plain object with Object.prototype, as in SSR. Tests compare enumeration against the SSR thenable, including reserved keys.

getNavigationContext()?.clientPageSearchParams ?? createClientPageSearchParams(null);
} else {
const urlSearchParams = useSearchParams();
searchParams = useMemo(() => createClientPageSearchParams(urlSearchParams), [urlSearchParams]);

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.

[P2] Preserve force-static semantics during browser navigation. This always reconstructs the prop from useSearchParams(), so a client page under dynamic = "force-static" can observe a destination query during a Link/router transition even though SSR supplies an empty object and Next.js keeps searchParams empty. The existing force-static hydration test only covers useSearchParams() on a hard load, not the page prop across client navigation; carry the force-static policy into this branch and cover that transition.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in be532d9. Dispatch (the main page, the intercept source and revalidation) and server-action rerenders now pass isForceStatic to the element builder. A force-static client page then gets an emptySearchParams prop, so SSR and the browser both give it an empty, untracked query, across client navigations too. Static export production builds get the same treatment. Covered by unit tests and a new e2e with a force-static client page doing a Link transition.

@ask-bonk

ask-bonk Bot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

Posted 2 actionable inline findings. Targeted tests passed: 157/157.

github run

@james-elicx
james-elicx force-pushed the isr-query/07-client-page-search-params branch from 7b4fee6 to 1d0047c Compare September 25, 2026 02:34
@james-elicx
james-elicx force-pushed the isr-query/07-client-page-search-params branch from 1d0047c to 0e66e0f Compare September 25, 2026 03:05
@james-elicx
james-elicx force-pushed the isr-query/07-client-page-search-params branch from 0e66e0f to 90fb1a2 Compare September 25, 2026 03:28
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Please review exact head 90fb1a25eff888a22a8b38e928c266cfe197cc40 without modifying or pushing the branch. Report all actionable findings within your 8-minute time limit.

@github-actions

Copy link
Copy Markdown
Contributor
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
response-store-demo preview production
workers-cache preview production
kv preview production
static-export preview production
web preview production

defineHiddenProperty(promise, "value", record);
for (const key of Object.keys(record)) {
if (isWellKnownProperty(key)) continue;
Reflect.defineProperty(promise, key, {

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.

[P2] Keep direct own-property checks consistent with SSR. On the server, makeThenableParams binds hasOwnProperty/propertyIsEnumerable to the underlying Promise target, where the virtual query keys do not exist; here the browser promise materializes those keys as own properties. With ?q=x, searchParams.hasOwnProperty("q") therefore renders false during SSR (without recording the read) and true during hydration. Please make these Object-prototype methods agree across both implementations and cover the direct-call case.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in e47396f. makeThenableParams now binds hasOwnProperty and propertyIsEnumerable to the proxy rather than the Promise target, so they go through its traps. They now answer like the browser record and mark the read. Tests cover the browser and SSR agreeing on a query key, a missing key and the reserved then, and the SSR call marking the render dynamic.

} else {
// Every App Router SSR render sets this. Without it there is no query this
// render may safely read, so the page gets an empty one.
searchParams =

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.

[P2] Avoid deciding the hydration query before a delayed client-page read can mark the render dynamic. The head bootstrap snapshots searchParamsGate.gate.decision; if a client page behind Suspense reads this thenable after the head was emitted, SSR uses the effective rewritten query but the bootstrap has already told the browser to recover the public URL query. A rewrite such as /alias -> /page?q=rewritten then hydrates different text. The query-source decision needs to remain correct for dynamism discovered after head emission, with a delayed-boundary rewrite hydration test.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 28287d5. After the gate settles, SSR keeps listening for the render turning dynamic. If the head hid the query and the render turns dynamic afterwards, the next flush sends the query client pages used, ahead of their HTML. That render can't be stored, so the document may carry it. The browser's hydration snapshot reads that value when a client page first renders. The new e2e "a client page behind a delayed boundary hydrates with the rewritten query" in static-hydration.spec.ts fails without the fix (hydration error #418).

@ask-bonk

ask-bonk Bot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

Posted 2 inline findings.

Additional findings:

  • [P1] app-browser-entry.ts:1749: server-action redirects ignore the target’s rendered rewrite query, giving client pages incorrect searchParams.
  • [P2] app-browser-entry.ts:592: supplemental slot/source refreshes discard rendered-query metadata, causing rewritten client pages to use the primary navigation query.

Targeted tests passed: 184/184.

github run

@james-elicx
james-elicx force-pushed the isr-query/07-client-page-search-params branch from 90fb1a2 to 8fcfc34 Compare September 25, 2026 04:12
@james-elicx

Copy link
Copy Markdown
Member Author

The two additional findings from the last review:

  • [P1] server-action redirects (app-browser-entry.ts). Fixed in 7d63ebe. resolveActionRedirectTarget now reads the redirect target's X-Vinext-Rendered-Path-And-Search, and renderRedirectPayload passes it to the navigation snapshot. So client pages get the target's rewritten query, as Next.js's router-driven redirect fetch gives them.
  • [P2] supplemental slot/source refreshes (app-browser-entry.ts). Fixed in 8fcfc34. Supplemental fetches record their rendered query (or their request target's query if the header is missing) against the elements they decoded. Slot provides it to ClientPageRoot, which prefers it over the primary snapshot. Test: "reads the query a refreshed kept branch rendered with".

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Please review exact head 8fcfc34876435745102cb8b6d41bb2fe188c9a69 without modifying or pushing the branch. Report all actionable findings within your 8-minute time limit.

@ask-bonk

ask-bonk Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

@james-elicx Bonk workflow was cancelled.

View workflow run · To retry, trigger Bonk again.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Please review exact head 8fcfc34876435745102cb8b6d41bb2fe188c9a69 without modifying or pushing the branch. Report all actionable findings within your 8-minute time limit.

@james-elicx
james-elicx force-pushed the isr-query/07-client-page-search-params branch from 8fcfc34 to 0242802 Compare September 25, 2026 04:44
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Please review exact head 0242802e13aa1f44188b840e60b6a45aecffb37f without modifying or pushing the branch. Report all actionable findings within your 8-minute time limit.

actionInitiation.href,
actionInitiation.routerState.navigationSnapshot.params,
// The re-render keeps the URL, so the server rendered the same query.
const navigationSnapshot = withRenderedSearchOf(

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.

[P1] Use the rendered query from the action response here. A same-URL action can still be rendered through a different effective query (for example a conditional middleware/config rewrite on the POST), and the response already carries X-Vinext-Rendered-Path-And-Search. Copying the pre-action snapshot makes ClientPageRoot in the fresh Flight tree read the old query, potentially mismatching the tree the server just rendered. Please parse that header for non-redirect action results and thread it into this snapshot too.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 63f22e9. The action re-render now sends X-Vinext-Rendered-Path-And-Search, and the client threads it into the same-URL snapshot. Covered by an e2e with a rewrite that only applies to actions.

createClientNavigationRenderSnapshot(
window.location.href,
latestClientParams,
getHydrationRenderedPathAndSearch(),

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.

[P2] Preserve the rendered query from the fetched response in the no-embedded-RSC fallback. In that branch readInitialRscStream() fetches a fresh RSC response but restores navigation context from window.location, ignoring X-Vinext-Rendered-Path-And-Search; this call therefore records the public query. For /alias -> /page?q=rewritten, the fallback tree gives its client page the alias query instead of the query used by the server. Parse the fallback response header before constructing this initial snapshot.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 8c68155. The fetched-payload fallback now restores the navigation context from the response's rendered-path header.

window.location.href,
latestClientParams,
// Same URL, so the same rendered query.
const navigationSnapshot = withRenderedSearchOf(

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.

[P3] Derive this value from the fresh HMR response rather than the previous tree. A conditional rewrite can resolve the same browser URL to a different query between requests, and HMR then installs newly rendered client-page props while this copied snapshot still supplies the old query. Keep the response available long enough to parse X-Vinext-Rendered-Path-And-Search, as the normal navigation path does.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in b3ae004. HMR now waits for the response headers and builds the snapshot from them. It also drops the update if a newer one started in the meantime.

// promise's species, so a query key of that name would break a real
// promise that carries the keys as own properties (the browser's client
// page searchParams).
"constructor",

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.

[P2] Do not add constructor to the shared reservation used by makeThenableParams. This changes existing server-page behavior for valid keys: a [constructor] segment or ?constructor=value now returns the Promise constructor from synchronous params.constructor/searchParams.constructor instead of the route value (while await params still returns the value). The Next.js well-known set also does not reserve this key. If the browser client-page promise needs it protected, use a browser-specific predicate rather than changing all params.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in a297b2d. constructor is out of the shared list, which matches Next.js's wellKnownProperties. The browser client-page promise is now a Proxy over a real promise, so a constructor query key reads synchronously like the SSR thenable.

@ask-bonk

ask-bonk Bot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

Posted 4 actionable inline findings.

Targeted tests passed: 47/47. Branch was not modified.

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Please review exact head f263a04b7d9d9dbe0263839699fb79b006b28e5c without modifying or pushing the branch. Report all actionable findings within your 8-minute time limit.

@ask-bonk

ask-bonk Bot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

LGTM!

github run

@james-elicx
james-elicx removed this pull request from stack #3467 September 25, 2026 08:54
@james-elicx
james-elicx added this pull request to stack #3471 September 25, 2026 08:54
…rved keys

Move the URLSearchParams-to-record loop into utils/query.ts and the
Promise/React reserved property list into a module free of server-only
imports, so browser code can build page searchParams the same way.
A client page no longer receives searchParams through Flight, like
Next.js's ClientPageRoot. Flight called `then` on the prop while
serializing it, so every client page was observed reading the query and
its RSC payload carried it.

- A "use client" ClientPageRoot shim renders the page. In SSR it reads a
  per-render thenable from the navigation context; a read marks the render
  dynamic, so a page that reads the query is never stored and one that
  doesn't stays cacheable. force-static and PPR fallback shells keep an
  untracked query.
- In the browser it builds a settled promise from useSearchParams(), with
  the query keys readable synchronously.
- Slot client pages drop the route searchParams from their Flight props.
… without a query

The markDynamic: hasRequestSearchParams exemption existed for client
pages, whose searchParams Flight always awaited. Class components were
only covered because isReactOwnedAppComponent groups them with client
references. Flight hands a class its props instead of serializing them,
so a read is a real read and follows function component pages.
…namic

The markDynamic option existed for the query-only exemption, which the
previous commit removed, so every caller now marks the render dynamic.

Also correct the class component rationale: React 19's Flight server
calls any function that isn't a client reference as a function
component, so an ES class page can't render in RSC. The branch only
stays consistent with function component pages.
…thenable

A page can tell the two apart during hydration, so they must agree:

- React's status and value on the browser promise are non-enumerable, so
  Object.keys() and spreading list the same query keys as the SSR proxy.
- The promise resolves to a plain object, so hasOwnProperty() works as in
  SSR. Spreading keeps a __proto__ key an own entry.
- constructor joins the reserved names. As an own property of a real
  promise it replaced Promise#constructor, and await threw.
…chParams empty

A client page's searchParams no longer travel through Flight, so the
browser didn't know the server renders force-static pages with an empty
query. A Link or router transition showed the destination query, where
Next.js keeps it empty.

- Dispatch and server action rerenders tell the page builder when a
  route is force-static. ClientPageRoot gets emptySearchParams, which is
  query-free, and hands the page an empty, untracked query in SSR and
  the browser.
- A static export build renders each page once without a query, and a
  client page read marked it dynamic, so the prerender skipped it and the
  export silently dropped it. Export builds get the same empty query, as
  before client pages stopped reading searchParams from Flight.
- The SSR thenable helper takes the render's force-static and PPR
  fallback shell state instead of a precomputed flag.
- Test a synchronous SSR read that is never stored, and a force-static
  page that reads and is stored with {}.
…at rendered them

The browser built a client page's searchParams from useSearchParams(),
so it followed the live URL:

- A rewritten query was lost. A soft navigation to /search/bar, rewritten
  to /search?q=bar, gave the page q undefined.
- A page that stays mounted followed later URLs. Opening an intercepted
  modal changed the background page's query, and so did a kept parallel
  slot.

Next.js reads the page's searchParams from its own segment payload. The
payload is query-free here, so the navigation snapshot now carries the
query the server rendered: X-Vinext-Rendered-Path-And-Search for fetched
and cached navigations, the embedded navigation payload on hydration,
and the current one for same-URL rerenders. ClientPageRoot captures it
the first time it renders a server output, keyed by the page's props
object, which Flight builds per server render and the router keeps with
the segment. A kept page no longer re-renders on each navigation.
…namic in the probe and prerender

SSR runs in a child scope of the render, so a client page's searchParams
read there reaches the request's dynamic latch but not the render's own
flag. Only candidate HTML renders read the latch, and neither the deploy
probe (never a candidate) nor the build prerender is one, so both called
the page static. Workers Cache then listed it as static-candidate and
admission answered its first render with the static-to-dynamic 500.

- Read the latch in every HTML render that decides whether the page is
  static: candidates, the Worker's probe and admission, and the build
  prerender, except PPR fallback shells.
- Let a speculative prerender wait for SSR to finish before its headers,
  stopping as soon as the render turns dynamic, so a read after the shell
  inside Suspense counts too.
…wser searchParams

The browser promise came with `status` and `value` already set, where the
SSR thenable only gets them once React tracks it. A client page reading
`searchParams.status` directly rendered `undefined` in SSR and `fulfilled`
in hydration, a mismatch. Next.js's browser and SSR promises are both a
plain `Promise.resolve(query)`, so neither carries them before `use()`.

- The browser promise no longer sets them, so `use()` tracks it as React
  does any promise.
- Test a direct read of both fields in SSR and hydration, as a unit render
  and in the production fixture. Browser unit renders wait for suspended
  pages.
…Components

With Cache Components, Next.js builds a client page's searchParams from
SearchParamsContext, the query useSearchParams() reads, not the one its
segment payload rendered (client/components/client-page.tsx). The browser
wrapper always read the rendered query, so a rewrite like
/feed/bar -> /feed?tab=bar exposed tab=bar, and a kept page kept its old
query when the URL changed.

- In that mode the browser wrapper reads useSearchParams(), so the page
  sees the public query and a kept page follows the URL. force-static and
  static export pages still read an empty query.
- Test a rewrite and a kept page with Cache Components.
…Flight payload

Without an embedded payload, hydration took both the pathname and the query
from `X-Vinext-Rendered-Path-And-Search`. That header names the rewrite
target, so for /alias -> /page?q=rewritten usePathname() hydrated as /page
where SSR rendered /alias, a mismatch. The RSC handler keeps the canonical
pathname and the resolved query apart, as the embedded payload does.

- Hydrate with the browser's pathname and only the header's query.
- Test the location it resolves, and usePathname() in the production
  fixture's fetched-payload hydration.
Every client page in an SSR render received the same promise. React writes
`status` and `value` onto a promise when a page calls use() on it, so a
sibling reading those fields rendered them in SSR, while the browser builds
one promise per page and the sibling hydrated with `undefined`.

Next.js keys its promise by the page's server-provided query object, in SSR
and the browser alike (`makeUntrackedSearchParams`), so both sides decode
the same payload into the same promises. The browser wrapper here keys by
the page's props object, so SSR now does too.

- The SSR navigation context carries a per-render source that caches one
  promise per page props object, and drops it with the render.
- Test a page using its query beside one reading the fields, as a unit
  render and in the production fixture.
@james-elicx
james-elicx force-pushed the isr-query/07-client-page-search-params branch from f263a04 to 4b18cd0 Compare September 25, 2026 09:19
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

Please review exact head 4b18cd070866ed70a953a572d721a7865e458a14 without modifying or pushing the branch. Report all actionable findings within your 8-minute time limit.

@ask-bonk

ask-bonk Bot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

LGTM!

github run

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