How to use nuqs without Suspense in Next.js?
Is there a way to use nuqs without requiring Suspense in Next.js App Router? How to avoid the Suspense boundary requirement?
Perfect! Now I have a complete picture. Let me provide a comprehensive answer based on the source code analysis.
##Answer
Yes, there are two ways to use nuqs without requiring Suspense boundaries for your entire page component:
###1. Use 'use client' Directive (Immediate Fix)
The quickest way is to mark your entire page as a client component. This avoids the Suspense boundary requirement because client components don't have the same SSR constraints as server components:
'use client'
import { useQueryState } from 'nuqs'
export default function Page() {
const [count, setCount] = useQueryState('count', parseAsInteger)
return (
<>
<p>Count: {count}</p>
<button onClick={() => setCount(c => (c ?? 0) + 1)}>+</button>
</>
)
}
###2. Use Server-Side Loader + Pass Promise to Child (Recommended)
This is the optimal approach for better UX. Instead of using client-side hooks in the page component, use the createLoader function to parse search params server-side, then pass the result to client components:
// app/search-params.ts
import { createLoader, parseAsInteger } from 'nuqs/server'
export const coordinatesSearchParams = {
latitude: parseAsInteger.withDefault(0),
longitude: parseAsInteger.withDefault(0)
}
export const loadSearchParams = createLoader(coordinatesSearchParams)
// app/page.tsx
import { loadSearchParams } from './search-params'
import type { SearchParams } from 'nuqs/server'
type PageProps = {
searchParams: Promise<SearchParams>
}
export default async function Page({ searchParams }: PageProps) {
// Option A: Await immediately (no Suspense needed)
const { latitude, longitude } = await loadSearchParams(searchParams)
return <Map lat={latitude} lng={longitude} />
// Option B: Pass the promise to children wrapped in Suspense (better UX)
// This streams the outer shell immediately while loading dynamic content
}
###Why Suspense is Required with useQueryState Hooks
Looking at the source code (packages/nuqs/src/adapters/next/impl.app.ts), the hooks use useSearchParams() from Next.js:
export function useNuqsNextAppRouterAdapter(): AdapterInterface {
const router = useRouter()
const searchParams = useSearchParams() // <- This requires Suspense
// ...
}
Since useSearchParams() is a client hook that triggers Client-Side Rendering (CSR), Next.js requires it to be wrapped in a Suspense boundary when used in server components. This is Next.js's way of handling hydration and SSR properly.
###Summary
| Approach | Use Case | Suspense Required |
|---|---|---|
'use client' on page | Simple client-side apps | ❌ No |
createLoader + await | Server-side parsing with no streaming | ❌ No |
createLoader + pass Promise to children | Optimal UX with progressive streaming | ✅ Yes, but only for children |
useQueryState in server components | ❌ Not recommended | ✅ Yes, mandatory |
The server-side loader approach (createLoader) is recommended because it allows you to handle search params on the server while maintaining the benefits of Server-Side Rendering (SSR) and streaming.
END OF POST