Modern web applications often represent UI state in the URL.
For example:
- search filters
- pagination
- sorting
- tab selection
- dashboard parameters
A typical URL might look like this:
/products?category=books&sort=price&page=2
Synchronizing UI state with the URL improves several things:
- shareability (users can share filtered views)
- bookmarking
- browser navigation
- predictable application state
However, implementing this in Next.js App Router often leads to repetitive and fragile code.
Recently I started using a library called nuqs, and it significantly improved the developer experience when managing URL state.
In this article I'll cover:
- why managing query parameters manually is painful
- why I chose nuqs
- how to use it in real projects
- what improved in my development workflow
The Problem: Manual URL State Management
Without a helper library, managing URL state usually requires combining:
- useSearchParams
- useRouter
- URLSearchParams
- useState
- useEffect
Example:
'use client'
import { useRouter, useSearchParams } from 'next/navigation'
import { useState, useEffect } from 'react'
export default function ProductFilter() {
const router = useRouter()
const searchParams = useSearchParams()
const [category, setCategory] = useState(
searchParams.get('category') || ''
)
useEffect(() => {
const params = new URLSearchParams(searchParams)
params.set('category', category)
router.push(`?${params.toString()}`)
}, [category])
return (
<select
value={category}
onChange={(e) => setCategory(e.target.value)}
>
<option value="">All</option>
<option value="books">Books</option>
<option value="games">Games</option>
</select>
)
}
While this works, it introduces several problems.
Problems ⚡️
• Boilerplate code everywhere
• Manual parsing of URLSearchParams
• Hard to scale with multiple filters
• Easy to introduce bugs
• No built-in type safety
As applications grow (for example search pages or dashboards), this logic quickly becomes hard to maintain.
What Is nuqs?
nuqs is a lightweight library that treats URL query parameters as React state.
Instead of manually synchronizing state and the URL, you simply use a hook.
Conceptually:
URL Query <-> React State
This drastically simplifies query parameter management.
Installing nuqs
npm install nuqs
That's it.
The Same Example Using nuqs
Here is the same filter example rewritten with nuqs.
'use client'
import { useQueryState } from 'nuqs'
export default function ProductFilter() {
const [category, setCategory] = useQueryState('category')
return (
<select
value={category ?? ''}
onChange={(e) => setCategory(e.target.value)}
>
<option value="">All</option>
<option value="books">Books</option>
<option value="games">Games</option>
</select>
)
}
The URL automatically updates when the state changes.
Example result:
/products?category=books
Compared to the manual implementation:
- no router logic
- no useEffect
- no manual query parsing
Type-Safe Query Parameters
One of the most powerful features of nuqs is its parser system.
Example: pagination.
import { useQueryState, parseAsInteger } from 'nuqs'
const [page, setPage] = useQueryState(
'page',
parseAsInteger.withDefault(1)
)
Benefits:
- page is always a number
- default value is 1
- invalid query values are handled safely
This prevents many runtime bugs.
Managing Multiple Query Parameters
Complex UIs usually have multiple filters.
nuqs handles this cleanly using useQueryStates.
import { useQueryStates, parseAsString } from 'nuqs'
const [filters, setFilters] = useQueryStates({
category: parseAsString,
sort: parseAsString
})
Updating the filters:
setFilters({
category: 'books',
sort: 'price'
})
Resulting URL:
?category=books&sort=price
This keeps filter logic predictable and easy to maintain.
Why I Chose nuqs
When selecting a library for URL state management, I considered several factors.
1. Developer Experience
nuqs removes the need to manually synchronize state and the URL.
Instead of managing useEffect, URLSearchParams, and router updates, query parameters behave like React state.
This dramatically reduces boilerplate and improves readability.
2. Type Safety
The parser system allows query parameters to be typed.
This ensures predictable values and reduces runtime errors in TypeScript projects.
3. Zero Dependencies and Lightweight
Another reason I chose nuqs is that it has zero runtime dependencies.
This is important for several reasons:
- smaller bundle size
- fewer potential security vulnerabilities
- lower risk of dependency conflicts
- easier long-term maintenance
In modern frontend projects, dependency trees can grow quickly.
Using focused libraries with minimal dependencies helps keep applications lean.
nuqs follows this philosophy very well.
4. Designed for Modern Next.js
nuqs integrates naturally with Next.js App Router and React hooks.
It fits well into modern React architectures without introducing additional complexity.
Real Impact in My Project
After introducing nuqs, several improvements became clear.
Less Boilerplate
Query state logic became significantly smaller and easier to understand.
Better Maintainability
Adding new filters now requires only a few lines.
Fewer Bugs
Type-safe parsing prevents invalid query states.
Better UX
Because the UI state lives in the URL:
- filters can be shared
- pages can be bookmarked
- browser navigation works naturally
When nuqs Is Especially Useful
From my experience, nuqs works especially well for:
- search/filter pages
- e-commerce listings
- dashboards
- analytics tools
- pagination systems
- sorting UIs
In other words:
✨ any interface where UI state should be reflected in the URL.
Final Thoughts
Managing URL state manually in Next.js often leads to repetitive and fragile code.
nuqs provides a clean abstraction that treats query parameters as React state while keeping everything type-safe and lightweight.
For modern React and Next.js applications, it can significantly improve both:
- developer experience
- code maintainability
If you're building filter-heavy interfaces or dashboards, I highly recommend giving nuqs a try!
Top comments (0)