Skip to content

fix(react): keep the visitor's query in useRouter().url on static pages - #479

Merged
Brooooooklyn merged 1 commit into
mainfrom
fix/react-static-query
Oct 18, 2026
Merged

Brooooooklyn merged 1 commit into
mainfrom
fix/react-static-query

Conversation

@Brooooooklyn

Copy link
Copy Markdown
Member

Fixes #449.

With output: 'static', the prerendered HTML and _void/pages/<page>.json hold the page's path with no query, because one file serves every query of that path. In React, RouteResource used the page data's URL whenever it differed from the browser URL, so:

open /ssg?foo=bar#baz         router.url → /ssg#baz              (query lost)
<Link href="#x">              router.url → /ssg#baz              (hash stale)
<Link href="/ssg?foo=two">    router.url → /ssg                  (query lost)

Vue, Svelte and Solid were not affected.

Change (React adapter only)

  • Navigation: when the page data has no query and belongs to the same page path as the visit, router.url keeps the visit's query. The hash comes from the browser after a same-page hash link, and from the visit during a reveal.
    • /a, /a/, /a.html and /a/index.html count as one page path, so the issue's local ssg.html?foo=bar repro works too. router.url keeps the page path there, as before.
    • Page data holds the decoded path (/café), so paths are compared in encoded form.
  • Hydration: the first render still uses the prerendered URL, so the HTML matches and React reports no Nuxt and Analog with void/nitro: missing nodejs_compat on native deploys, incomplete init notes, and test, type and size gaps #418 mismatch. The hydration commit switches to the browser URL before effects run, so a mount-only effect sees the query. The re-render runs in a transition, which keeps the server HTML of Suspense boundaries that have not hydrated yet.
  • Unchanged: pages rendered only in the browser (ssr = false) use the full URL from the first render. Server-rendered pages already have the query in their page data, so they take the existing exact-match branch.

Tests

node-static.test.ts › "keeps the query string in router.url on a prerendered React page" uses new playground/node-static pages (router-url/[slug], router-url-client). It checks, with no page error and no hydration error:

Visit router.url
/router-url/plain?foo=bar#baz after hydration /router-url/plain?foo=bar#baz, and a mount-only effect sees ?foo=bar
then <Link href="#details"> /router-url/plain?foo=bar#details
then <Link href="…?foo=two"> /router-url/plain?foo=two
/router-url/a%20b?x=1#a, then the hash link /router-url/a%20b?x=1#a → …#details
/router-url/plain.html?foo=bar#baz /router-url/plain?foo=bar#baz
/router-url-client?foo=bar (ssr = false) first render has ?foo=bar

It also checks that a lazy Suspense child keeps its server node through the switch.

Mutation checks: each part fails the test when undone (the navigation branch, the initial fetchUrl, the hydration start URL, the transition, encoding, the .html path rule, the ssr = false check).

Local runs used the installed Chrome, because the Playwright Chromium download is blocked here. These pass: all packages/react tests (536 with the deploy unit test), React and void typecheck, lint and format.

Known limit

  • A server redirect that stays on the same path but drops the query (/a?x=1 → /a) shows the old query for one render, until the navigation finishes. Fixing it would need the shared navigation engine to expose the redirect target, so it is left out.

🤖 Generated with Claude Code

With `output: 'static'`, the prerendered HTML and its page data hold the
path without a query, and serve every query of that path. The React page
router used the page data's URL whenever it differed from the browser
URL, so `useRouter().url` dropped the query on load and after `Link`
navigation, and kept a stale hash after a same-page hash link.

When the page data has no query and belongs to the same page path,
`router.url` now keeps the query of the visit. `/a`, `/a/`, `/a.html`
and `/a/index.html` count as one page path, and paths are compared in
encoded form. Hydration still renders with the prerendered URL, so the
HTML matches. The commit then switches to the browser URL before
effects run and re-renders in a transition, which keeps the server HTML
of Suspense boundaries that have not hydrated yet.

Fixes #449

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-18T15:45:36.572931Z 728fa09 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://fix-react-static-query-void-cloud.voidzero-docs.workers.dev (commit 728fa09)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://198dd533-void-cloud.voidzero-docs.workers.dev 728fa09 2026-10-18T15:43:36.653Z Visit the dashboard ↗

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: 728fa095dd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Brooooooklyn
Brooooooklyn merged commit 43c7fe1 into main Oct 18, 2026
20 checks passed
@Brooooooklyn
Brooooooklyn deleted the fix/react-static-query branch October 18, 2026 16:17
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.

Static output drops query parameters from useRouter().url after hydration

1 participant