DEV Community

Cover image for ๐ŸŒ Stop Fighting Next.js Search Params: Use nuqs for Type-Safe URL State
Theophilus K. Dadzie
Theophilus K. Dadzie

Posted on

๐ŸŒ Stop Fighting Next.js Search Params: Use nuqs for Type-Safe URL State

The Hidden Pain of URL State Management

We all know the scenario: you're building a dashboard, an e-commerce site, or any application with filters, pagination, or search. Where do you store the state for these features?

  • Local React State? Breaks page reloads and link-sharing (the user loses their filters).
  • Global State (like Redux/Zustand)? Overkill for simple URL state, adds complexity, and still doesn't automatically sync with the URL.
  • Manual Next.js useSearchParams? ๐Ÿ˜ฉ Tedious string manipulation, no type safety, and you have to manually handle router.push/router.replace with every single change.

In the Next.js App Router era, managing URL search parameters should feel like managing simple React state declarative, type-safe, and without boilerplate. That's exactly what nuqs (Next.js URL Query State) delivers.

This article dives into how nuqs turns complex query parameter logic into two lines of beautiful, shareable code.

๐Ÿ› ๏ธ Installation and Setup

Getting started with nuqs is fast. It works natively with the Next.js App Router and even supports React Server Components (RSC) cleanly.

npm install nuqs
Enter fullscreen mode Exit fullscreen mode

For the App Router, you'll want to wrap your root layout with the adapter to ensure all updates work seamlessly:

app/layout.tsx

import { NuqsAdapter } from 'nuqs/adapters/next/app';
import { type ReactNode } from 'react';

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="en">
      <body>
        <NuqsAdapter>{children}</NuqsAdapter>
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

The Core Idea: useQueryState
The entire power of nuqs comes from one hook: useQueryState. It mirrors the familiar React.useState hook but automatically synchronizes its value with a specific URL search parameter.

Before nuqs

const searchParams = useSearchParams();
const page = Number(searchParams.get("page")) || 1;

// Updating is even worse:
const params = new URLSearchParams(searchParams);
params.set("page", String(page + 1));

router.push(`?${params.toString()}`);

Enter fullscreen mode Exit fullscreen mode

Messy. Repetitive. Bug-prone.

After nuqs

const [page, setPage] = useQueryState("page", parseAsInteger.withDefault(1));

setPage(page + 1);
Enter fullscreen mode Exit fullscreen mode

Clean. Declarative. Beautiful.

Pagination Example

const [page, setPage] = useQueryState(
  "page",
  parseAsInteger.withDefault(1)
);

<button onClick={() => setPage(page + 1)}>Next Page</button>

Enter fullscreen mode Exit fullscreen mode

Simple. State-like. No URL wrangling.

Arrays, Booleans, Enums: No Problem
nuqs includes parsers:

const [tags, setTags] = useQueryState(
  "tags",
  parseAsArray(parseAsString)
);

Enter fullscreen mode Exit fullscreen mode

OR

const [categories, setCategories] = useQueryState(
  "categories",
  parseAsArray(parseAsString).withDefault([])
);

Enter fullscreen mode Exit fullscreen mode

๐Ÿ”ฌ Advanced Power: Parsing, Debouncing, and Defaults

nuqs provides incredible features that are often required for a polished user experience.

  1. Type-Safe Number Parsing

Query parameters are always strings, but your state should be a number (e.g., for page).

import { useQueryState, parseAsInteger } from 'nuqs';

// URL: /dashboard?page=5
// page is guaranteed to be either a number (5) or null.
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1)); 
// .withDefault(1) ensures 'page' is always 1 if not present.
Enter fullscreen mode Exit fullscreen mode

This is a massive win for Type Safety and eliminates messy parseInt() logic.

2.โณ Debouncing URL Updates (Super Useful!)

A search bar is the classic use case for debouncing. You don't want the URL to update or re-fetch data on every keystroke.

const [query, setQuery] = useQueryState(
  "q",
  parseAsString.withOptions({ throttleMs: 300 })
);

Enter fullscreen mode Exit fullscreen mode

This is great for search inputs so you donโ€™t spam history entries.

OR

"use client";
import { useQueryState, parseAsString } from "nuqs";

export default function SearchBox() {
  const [query, setQuery] = useQueryState("q", parseAsString);

  return (
    <input
      value={query || ""}
      onChange={(e) => setQuery(e.target.value)}
      placeholder="Searchโ€ฆ"
      className="border px-2 py-1 rounded"
    />
  );
}

Enter fullscreen mode Exit fullscreen mode

The state in your component updates immediately for a smooth input, but the URL only updates when they pause, saving bandwidth and preventing server spam.

3. Managing Multiple Keys with useQueryStates

If you have several related filters (e.g., sort, direction, page), you can update them all at once.

import { useQueryStates, parseAsString, parseAsInteger } from 'nuqs';

const [{ sort, direction }, setQueries] = useQueryStates({
  sort: parseAsString.withDefault('name'),
  direction: parseAsString.withDefault('asc'),
});

// Update both keys in a single, atomic operation:
setQueries({ 
  sort: 'price', 
  direction: 'desc' 
});
Enter fullscreen mode Exit fullscreen mode

Here is the revised "Why Choose nuqs?" section in a clean, Markdown-formatted table, ready for you to copy and paste directly into your Dev.to blog post.

Why Choose nuqs?


Feature Next.js Manual Search Params nuqs
Type Safety No (everything is a string/null) Yes (parsers guarantee types like number, boolean, array)
Boilerplate High (manual URLSearchParams, router.replace) Low (one hook call per param)
Debouncing/Throttling Manual implementation required Built-in as an easy option
SSR/RSC Compatibility Native but requires manual syncing Native and handles hydration cleanly
Shareability Yes Yes, built-in by design

Would you like to add any other sections to the blog post, such as a brief section on how to use array or boolean parsing with nuqs?

If you're building stateful, highly interactive Next.js apps in the App Router, nuqs is an absolute game-changer for developer experience. It cleans up your components, simplifies your logic, and makes your application URL-shareable by default.

Top comments (0)