[PR #424] [MERGED] fix: return ReadonlyURLSearchParams from useSearchParams #562

Closed
opened 2026-05-06 13:08:46 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/424
Author: @JaredStowell
Created: 3/10/2026
Status: Merged
Merged: 3/10/2026
Merged by: @james-elicx

Base: mainHead: jstowell/fix-readonly-search-params


📝 Commits (2)

  • 493ba1e Update navigation search params
  • 4eb5652 Fix server readonly cache scope

📊 Changes

8 files changed (+335 additions, -27 deletions)

View changed files

📝 packages/vinext/src/shims/navigation.ts (+30 -19)
📝 packages/vinext/src/shims/next-shims.d.ts (+7 -3)
packages/vinext/src/shims/readonly-url-search-params.ts (+29 -0)
📝 tests/e2e/app-router/nextjs-compat/hooks.spec.ts (+32 -0)
tests/fixtures/app-basic/app/nextjs-compat/hooks-search-readonly/page.tsx (+85 -0)
📝 tests/fixtures/app-basic/next-shims.d.ts (+7 -1)
📝 tests/nextjs-compat/hooks.test.ts (+29 -0)
📝 tests/shims.test.ts (+116 -4)

📄 Description

Summary

Fix next/navigation compatibility so useSearchParams() returns a real ReadonlyURLSearchParams wrapper instead of a mutable URLSearchParams.

This matches Next.js runtime behavior more closely:

  • exports a runtime ReadonlyURLSearchParams class
  • throws on append, delete, set, and sort
  • returns the readonly wrapper from useSearchParams() on both SSR and client paths
  • preserves stable snapshot identity for useSyncExternalStore

Changes

  • add packages/vinext/src/shims/readonly-url-search-params.ts
  • update packages/vinext/src/shims/navigation.ts to cache and return readonly wrappers
  • update packages/vinext/src/shims/next-shims.d.ts to expose the runtime class and readonly return type
  • update fixture shim declarations in tests/fixtures/app-basic/next-shims.d.ts
  • add compat fixture tests/fixtures/app-basic/app/nextjs-compat/hooks-search-readonly/page.tsx
  • add unit coverage for readonly wrapper behavior and SSR hook behavior
  • add Next.js-compat SSR coverage for instanceof ReadonlyURLSearchParams and mutation blocking
  • add Playwright coverage for hydrated client behavior

Next.js parity

Ported from:

  • test/e2e/app-dir/hooks/hooks.test.ts
  • test/e2e/app-dir/hooks/app/hooks/use-search-params/instanceof/page.js

Reference implementation:

  • packages/next/src/client/components/readonly-url-search-params.ts
  • packages/next/src/client/components/navigation.ts

Verification

  • pnpm run fmt
  • pnpm run build
  • pnpm test tests/shims.test.ts tests/nextjs-compat/hooks.test.ts
  • PLAYWRIGHT_PROJECT=app-router pnpm run test:e2e tests/e2e/app-router/nextjs-compat/hooks.spec.ts
  • pnpm run typecheck
  • pnpm run lint
  • pnpm test

Notes

This is a behavior change for any app that was incorrectly mutating the object returned by useSearchParams(), but that previous behavior was not Next.js-compatible


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/424 **Author:** [@JaredStowell](https://github.com/JaredStowell) **Created:** 3/10/2026 **Status:** ✅ Merged **Merged:** 3/10/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `jstowell/fix-readonly-search-params` --- ### 📝 Commits (2) - [`493ba1e`](https://github.com/cloudflare/vinext/commit/493ba1e7f9022fa34ef789e9cb0c8bff55814860) Update navigation search params - [`4eb5652`](https://github.com/cloudflare/vinext/commit/4eb56529b31c243975611c729212e1a7b9927ae8) Fix server readonly cache scope ### 📊 Changes **8 files changed** (+335 additions, -27 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/shims/navigation.ts` (+30 -19) 📝 `packages/vinext/src/shims/next-shims.d.ts` (+7 -3) ➕ `packages/vinext/src/shims/readonly-url-search-params.ts` (+29 -0) 📝 `tests/e2e/app-router/nextjs-compat/hooks.spec.ts` (+32 -0) ➕ `tests/fixtures/app-basic/app/nextjs-compat/hooks-search-readonly/page.tsx` (+85 -0) 📝 `tests/fixtures/app-basic/next-shims.d.ts` (+7 -1) 📝 `tests/nextjs-compat/hooks.test.ts` (+29 -0) 📝 `tests/shims.test.ts` (+116 -4) </details> ### 📄 Description ## Summary Fix `next/navigation` compatibility so `useSearchParams()` returns a real `ReadonlyURLSearchParams` wrapper instead of a mutable `URLSearchParams`. This matches Next.js runtime behavior more closely: - exports a runtime `ReadonlyURLSearchParams` class - throws on `append`, `delete`, `set`, and `sort` - returns the readonly wrapper from `useSearchParams()` on both SSR and client paths - preserves stable snapshot identity for `useSyncExternalStore` ## Changes - add `packages/vinext/src/shims/readonly-url-search-params.ts` - update `packages/vinext/src/shims/navigation.ts` to cache and return readonly wrappers - update `packages/vinext/src/shims/next-shims.d.ts` to expose the runtime class and readonly return type - update fixture shim declarations in `tests/fixtures/app-basic/next-shims.d.ts` - add compat fixture `tests/fixtures/app-basic/app/nextjs-compat/hooks-search-readonly/page.tsx` - add unit coverage for readonly wrapper behavior and SSR hook behavior - add Next.js-compat SSR coverage for `instanceof ReadonlyURLSearchParams` and mutation blocking - add Playwright coverage for hydrated client behavior ## Next.js parity Ported from: - `test/e2e/app-dir/hooks/hooks.test.ts` - `test/e2e/app-dir/hooks/app/hooks/use-search-params/instanceof/page.js` Reference implementation: - `packages/next/src/client/components/readonly-url-search-params.ts` - `packages/next/src/client/components/navigation.ts` ## Verification - `pnpm run fmt` - `pnpm run build` - `pnpm test tests/shims.test.ts tests/nextjs-compat/hooks.test.ts` - `PLAYWRIGHT_PROJECT=app-router pnpm run test:e2e tests/e2e/app-router/nextjs-compat/hooks.spec.ts` - `pnpm run typecheck` - `pnpm run lint` - `pnpm test` ## Notes This is a behavior change for any app that was incorrectly mutating the object returned by `useSearchParams()`, but that previous behavior was not Next.js-compatible --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:08:46 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#562
No description provided.