diff --git a/packages/e2e/tanstack-router/cypress/e2e/trailing-slash.cy.ts b/packages/e2e/tanstack-router/cypress/e2e/trailing-slash.cy.ts
new file mode 100644
index 000000000..c14a6afc5
--- /dev/null
+++ b/packages/e2e/tanstack-router/cypress/e2e/trailing-slash.cy.ts
@@ -0,0 +1,12 @@
+it('does not append a trailing slash', () => {
+ cy.visit('/trailing-slash')
+ cy.location('pathname').should('eq', '/trailing-slash')
+ cy.contains('Set declared').click()
+ cy.location('pathname').should('eq', '/trailing-slash')
+ cy.contains('Clear').click()
+ cy.location('pathname').should('eq', '/trailing-slash')
+ cy.contains('Set undeclared').click()
+ cy.location('pathname').should('eq', '/trailing-slash')
+ cy.contains('Clear').click()
+ cy.location('pathname').should('eq', '/trailing-slash')
+})
diff --git a/packages/e2e/tanstack-router/src/routes/trailing-slash.tsx b/packages/e2e/tanstack-router/src/routes/trailing-slash.tsx
new file mode 100644
index 000000000..1bb989ad0
--- /dev/null
+++ b/packages/e2e/tanstack-router/src/routes/trailing-slash.tsx
@@ -0,0 +1,50 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { createStandardSchemaV1, parseAsString, useQueryStates } from 'nuqs'
+
+const declared = {
+ declared: parseAsString
+}
+const undeclared = {
+ undeclared: parseAsString
+}
+
+const searchParams = {
+ ...declared,
+ ...undeclared
+}
+
+export const Route = createFileRoute('/trailing-slash')({
+ validateSearch: createStandardSchemaV1(declared, {
+ partialOutput: true
+ }),
+ component: TrailingSlashTest
+})
+
+function TrailingSlashTest() {
+ const [{ declared, undeclared }, setSearchParams] =
+ useQueryStates(searchParams)
+ return (
+
+
+
+
+
{JSON.stringify({ declared, undeclared }, null, 2)}
+
+ )
+}
diff --git a/packages/nuqs/src/adapters/tanstack-router.ts b/packages/nuqs/src/adapters/tanstack-router.ts
index 3ec90639b..91475d436 100644
--- a/packages/nuqs/src/adapters/tanstack-router.ts
+++ b/packages/nuqs/src/adapters/tanstack-router.ts
@@ -1,10 +1,11 @@
-import { useLocation, useMatches, useNavigate } from '@tanstack/react-router'
+import { useLocation, useNavigate } from '@tanstack/react-router'
import { startTransition, useCallback, useMemo } from 'react'
import { renderQueryString } from '../lib/url-encoding'
import { createAdapterProvider, type AdapterProvider } from './lib/context'
import type { AdapterInterface, UpdateUrlFunction } from './lib/defs'
function useNuqsTanstackRouterAdapter(watchKeys: string[]): AdapterInterface {
+ const pathname = useLocation({ select: state => state.pathname })
const search = useLocation({
select: state =>
Object.fromEntries(
@@ -12,12 +13,6 @@ function useNuqsTanstackRouterAdapter(watchKeys: string[]): AdapterInterface {
)
})
const navigate = useNavigate()
- const from = useMatches({
- select: matches =>
- matches.length > 0
- ? (matches[matches.length - 1]?.fullPath as string)
- : undefined
- })
const searchParams = useMemo(
() =>
// search is a Record>,
@@ -56,20 +51,18 @@ function useNuqsTanstackRouterAdapter(watchKeys: string[]): AdapterInterface {
// TBC if it causes issues with consuming those search params
// in other parts of the app.
//
- // When we clear the search, passing an empty string causes
- // a type error and possible basepath issues, so we switch it to '.' instead.
- // See https://github.com/47ng/nuqs/pull/953#issuecomment-3003583471
- to: renderQueryString(search) || '.',
- // `from` will be handled by tanstack router match resolver, code snippet:
- // https://github.com/TanStack/router/blob/5d940e2d8bdb12e213eede0abe8012855433ec4b/packages/react-router/src/link.tsx#L108-L112
- ...(from ? { from } : {}),
+ // Note: we need to specify pathname + search here to avoid TSR appending
+ // a trailing slash to the pathname, see https://github.com/47ng/nuqs/issues/1215
+ from: '/',
+ to: pathname + renderQueryString(search),
replace: options.history === 'replace',
resetScroll: options.scroll,
- hash: prevHash => prevHash ?? ''
+ hash: prevHash => prevHash ?? '',
+ state: state => state
})
})
},
- [navigate, from]
+ [navigate, pathname]
)
return {