diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml
index 9ae7f4e0..33c36e66 100644
--- a/.github/workflows/ci-cd.yml
+++ b/.github/workflows/ci-cd.yml
@@ -28,18 +28,15 @@ jobs:
- '13.5'
- '14.0.1'
# 14.0.2 is not compatible due to a prefetch issue
- - latest
- - '14.0.5-canary.54' # WHS stabilised
+ # 14.0.3 requires the WHS flag (see below)
+ - '14.0.4'
+ - latest # Current latest is 14.1.0
include:
- # 14.0.3 requires the WHS flag
- next-version: '14.0.3'
window-history-support: true
- # Current latest is 14.0.4
- - next-version: latest
+ # 14.0.4 doesn't require the WHS flag, but supports it
+ - next-version: '14.0.4'
window-history-support: true
- - next-version: latest
- window-history-support: true
- base-path: '/base'
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
diff --git a/packages/e2e/cypress.config.ts b/packages/e2e/cypress.config.ts
index c452562c..d17c5485 100644
--- a/packages/e2e/cypress.config.ts
+++ b/packages/e2e/cypress.config.ts
@@ -5,7 +5,9 @@ import semver from 'semver'
const basePath =
process.env.BASE_PATH === '/' ? '' : process.env.BASE_PATH ?? ''
-const windowHistorySupport = supportsWHS()
+const nextJsVersion = readNextJsVersion()
+
+const windowHistorySupport = supportsWHS(nextJsVersion)
? process.env.WINDOW_HISTORY_SUPPORT === 'true'
? 'true'
: 'false'
@@ -21,17 +23,25 @@ export default defineConfig({
retries: 2,
env: {
basePath,
- windowHistorySupport
+ windowHistorySupport,
+ supportsShallowRouting: supportsShallowRouting(nextJsVersion)
}
}
})
-function supportsWHS() {
+function readNextJsVersion() {
const pkgPath = new URL('./package.json', import.meta.url)
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
- const nextVersion: string = pkg.dependencies.next
+ return pkg.dependencies.next
+}
+
+function supportsWHS(nextVersion: string) {
return (
semver.gte(nextVersion, '14.0.3-canary.6') &&
semver.lt(nextVersion, '14.0.5-canary.54')
)
}
+
+function supportsShallowRouting(nextVersion: string) {
+ return semver.gte(nextVersion, '14.1.0')
+}
diff --git a/packages/e2e/cypress/e2e/useSearchParams.cy.js b/packages/e2e/cypress/e2e/useSearchParams.cy.js
new file mode 100644
index 00000000..1e252735
--- /dev/null
+++ b/packages/e2e/cypress/e2e/useSearchParams.cy.js
@@ -0,0 +1,12 @@
+///
+
+if (Cypress.env('supportsShallowRouting')) {
+ it('useSearchParams', () => {
+ cy.visit('/app/useSearchParams')
+ cy.contains('#hydration-marker', 'hydrated').should('be.hidden')
+ cy.get('input').type('foo', { delay: 0 })
+ cy.get('#searchParams').should('have.text', 'q=foo')
+ cy.get('button').click()
+ cy.get('#searchParams').should('have.text', 'q=foo&push=true')
+ })
+}
diff --git a/packages/e2e/src/app/app/useSearchParams/page.tsx b/packages/e2e/src/app/app/useSearchParams/page.tsx
new file mode 100644
index 00000000..899dc91e
--- /dev/null
+++ b/packages/e2e/src/app/app/useSearchParams/page.tsx
@@ -0,0 +1,29 @@
+'use client'
+
+import { useSearchParams } from 'next/navigation'
+import { parseAsBoolean, useQueryState } from 'nuqs'
+import { Suspense } from 'react'
+
+export default function Page() {
+ return (
+
+
+
+ )
+}
+
+function Client() {
+ const [q, setQ] = useQueryState('q', { defaultValue: '' })
+ const [, setPush] = useQueryState(
+ 'push',
+ parseAsBoolean.withDefault(false).withOptions({ history: 'push' })
+ )
+ const searchParams = useSearchParams()
+ return (
+
+
setQ(e.target.value)} />
+
{searchParams?.toString() ?? 'null'}
+
+
+ )
+}
diff --git a/packages/nuqs/src/update-queue.ts b/packages/nuqs/src/update-queue.ts
index 3596b701..c16a34a6 100644
--- a/packages/nuqs/src/update-queue.ts
+++ b/packages/nuqs/src/update-queue.ts
@@ -180,9 +180,16 @@ function flushUpdateQueue(router: Router): [URLSearchParams, null | unknown] {
// this allows keeping a reactive URL if the network is slow.
const updateMethod =
options.history === 'push' ? history.pushState : history.replaceState
+ // In 14.1.0, useSearchParams becomes reactive to shallow updates,
+ // but only if passing `null` as the history state.
+ // Older versions need to maintain the history state for push to work.
+ // This should theoretically be checking for >=14.0.5-canary.54 where WHS
+ // was stabilised, but we're not supporting canaries from previous GAs.
+ const state =
+ (window.next?.version ?? '') >= '14.1.0' ? null : history.state
updateMethod.call(
history,
- history.state,
+ state,
// Our own updates have a marker to prevent syncing
// when the URL changes (we've already sync'd them up
// via `emitter.emit(key, newValue)` above, without
@@ -198,9 +205,7 @@ function flushUpdateQueue(router: Router): [URLSearchParams, null | unknown] {
// Call the Next.js router to perform a network request
// and re-render server components.
router.replace(url, {
- scroll: false,
- // @ts-expect-error - pages router fix, but not exposed in navigation types
- shallow: false
+ scroll: false
})
})
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9eb86ef0..7f14a340 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3840,18 +3840,6 @@ packages:
supports-color: 8.1.1
dev: true
- /debug@4.3.4:
- resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
- engines: {node: '>=6.0'}
- peerDependencies:
- supports-color: '*'
- peerDependenciesMeta:
- supports-color:
- optional: true
- dependencies:
- ms: 2.1.2
- dev: false
-
/debug@4.3.4(supports-color@8.1.1):
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
@@ -3863,7 +3851,6 @@ packages:
dependencies:
ms: 2.1.2
supports-color: 8.1.1
- dev: true
/decamelize-keys@1.1.1:
resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==}
@@ -4636,7 +4623,6 @@ packages:
/has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
- dev: true
/hast-util-from-html@2.0.1:
resolution: {integrity: sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==}
@@ -6052,7 +6038,7 @@ packages:
resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==}
dependencies:
'@types/debug': 4.1.12
- debug: 4.3.4
+ debug: 4.3.4(supports-color@8.1.1)
decode-named-character-reference: 1.0.2
devlop: 1.1.0
micromark-core-commonmark: 2.0.0
@@ -8048,7 +8034,6 @@ packages:
engines: {node: '>=10'}
dependencies:
has-flag: 4.0.0
- dev: true
/supports-hyperlinks@2.3.0:
resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==}