Skip to main content

What are adapters?

Adapters are framework-specific integration layers that enable nuqs to work seamlessly across different React frameworks. They bridge the gap between nuqs’s core URL state management functionality and each framework’s unique routing and navigation systems.

Why are adapters needed?

Different React frameworks handle routing and URL updates in fundamentally different ways:
  • Next.js App Router uses React Server Components and navigation events
  • Next.js Pages Router relies on the next/router module
  • Plain React SPAs work directly with the browser’s History API
  • Remix and React Router have their own navigation paradigms
  • TanStack Router provides its own state management approach
Without adapters, nuqs would need to detect and handle these differences in every hook call, leading to a bloated bundle and maintenance nightmare. Adapters provide a clean abstraction that keeps the core library framework-agnostic.

How adapters work

Every adapter implements the AdapterInterface, which requires two core capabilities:
type AdapterInterface = {
  // Current URL search parameters
  searchParams: URLSearchParams
  
  // Function to update the URL
  updateUrl: (search: URLSearchParams, options: AdapterOptions) => void
  
  // Optional: Get current search params snapshot
  getSearchParamsSnapshot?: () => URLSearchParams
  
  // Optional: Rate limit factor for throttling
  rateLimitFactor?: number
  
  // Optional: Auto-reset queue behavior
  autoResetQueueOnUpdate?: boolean
}
From packages/nuqs/src/adapters/lib/defs.ts:12-18

Available adapters

Next.js App Router

import { NuqsAdapter } from 'nuqs/adapters/next/app'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <NuqsAdapter>{children}</NuqsAdapter>
      </body>
    </html>
  )
}
Supported versions: Next.js >=14.2.0. For older versions, install nuqs@^1.

Next.js Pages Router

import { NuqsAdapter } from 'nuqs/adapters/next/pages'
import type { AppProps } from 'next/app'

export default function MyApp({ Component, pageProps }: AppProps) {
  return (
    <NuqsAdapter>
      <Component {...pageProps} />
    </NuqsAdapter>
  )
}

Plain React (SPA)

For Vite, Create React App, or any React-based SPA:
import { NuqsAdapter } from 'nuqs/adapters/react'
import { createRoot } from 'react-dom/client'

createRoot(document.getElementById('root')!).render(
  <NuqsAdapter>
    <App />
  </NuqsAdapter>
)
The React adapter supports an optional prop for full-page navigation:
<NuqsAdapter fullPageNavigationOnShallowFalseUpdates={true}>
  <App />
</NuqsAdapter>
When enabled, setting shallow: false will trigger a full page reload instead of just a client-side navigation.

Remix

import { NuqsAdapter } from 'nuqs/adapters/remix'
import { Outlet } from '@remix-run/react'

export default function App() {
  return (
    <NuqsAdapter>
      <Outlet />
    </NuqsAdapter>
  )
}
Supported versions: @remix-run/react@>=2

React Router v6

import { NuqsAdapter } from 'nuqs/adapters/react-router/v6'
import { createBrowserRouter, RouterProvider } from 'react-router-dom'

const router = createBrowserRouter([
  { path: '/', element: <App /> }
])

export function Root() {
  return (
    <NuqsAdapter>
      <RouterProvider router={router} />
    </NuqsAdapter>
  )
}

React Router v7

import { NuqsAdapter } from 'nuqs/adapters/react-router/v7'
import { Outlet } from 'react-router'

export default function App() {
  return (
    <NuqsAdapter>
      <Outlet />
    </NuqsAdapter>
  )
}

TanStack Router

import { NuqsAdapter } from 'nuqs/adapters/tanstack-router'
import { Outlet, createRootRoute } from '@tanstack/react-router'

export const Route = createRootRoute({
  component: () => (
    <NuqsAdapter>
      <Outlet />
    </NuqsAdapter>
  )
})
TanStack Router support is experimental and does not yet cover TanStack Start.

Testing adapter

For unit testing components that use nuqs hooks:
import { render } from '@testing-library/react'
import { NuqsTestingAdapter } from 'nuqs/adapters/testing'
import { describe, it, expect, vi } from 'vitest'

it('should update search params', async () => {
  const onUrlUpdate = vi.fn()
  
  render(<MyComponent />, {
    wrapper: ({ children }) => (
      <NuqsTestingAdapter 
        searchParams="?count=42" 
        onUrlUpdate={onUrlUpdate}
      >
        {children}
      </NuqsTestingAdapter>
    )
  })
  
  // Test your component...
  expect(onUrlUpdate).toHaveBeenCalled()
})
From the README examples.

Creating custom adapters

You can create custom adapters for unsupported frameworks using the unstable_createAdapterProvider function:
import { 
  unstable_createAdapterProvider,
  type unstable_AdapterInterface 
} from 'nuqs/adapters/custom'

function useMyFrameworkAdapter(watchKeys: string[]): unstable_AdapterInterface {
  // Implement the adapter interface
  return {
    searchParams: /* URLSearchParams from your framework */,
    updateUrl: (search, options) => {
      // Update URL using your framework's navigation
    }
  }
}

export const NuqsAdapter = unstable_createAdapterProvider(useMyFrameworkAdapter)
Custom adapter APIs are marked as unstable_ and may change in future versions.

Adapter selection

The Next.js adapter automatically detects whether you’re using the app router or pages router at runtime:
// From packages/nuqs/src/adapters/next.ts
function useNuqsNextAdapter(): AdapterInterface {
  const pagesRouterImpl = useNuqsNextPagesRouterAdapter()
  const appRouterImpl = useNuqsNextAppRouterAdapter()
  
  return {
    searchParams: appRouterImpl.searchParams,
    updateUrl(search, options) {
      if (isPagesRouter()) {
        return pagesRouterImpl.updateUrl(search, options)
      } else {
        return appRouterImpl.updateUrl(search, options)
      }
    },
    autoResetQueueOnUpdate: false
  }
}
From packages/nuqs/src/adapters/next.ts:6-20

Build docs developers (and LLMs) love