OMAR
Field NotesCV
Put Filter State in the URL, Not in useState
← All Notes
Frontend04 August 2026 · 3 min read

Put Filter State in the URL, Not in useState

If a filtered view cannot be bookmarked, shared or reloaded, it is a bug — not a design choice. nuqs makes the URL the state, with types.

Open a product catalogue, pick three filters, find the thing you wanted, and send the link to a colleague. They open the unfiltered catalogue. Reload the page yourself and your filters are gone.

That is not a minor annoyance. It is a support burden ("which filters did you use?"), an analytics blind spot (every filtered view is the same URL), and on public catalogues a pile of pages search engines can never see.

The state was always meant to live in the URL. The reason it does not is that reading and writing query strings by hand is tedious and everything comes back as a string.

Same API, different storage

import { useQueryState, parseAsInteger, parseAsArrayOf, parseAsString } from 'nuqs'

const [query, setQuery] = useQueryState('q', { defaultValue: '' })
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1))
const [tags, setTags] = useQueryState('tags', parseAsArrayOf(parseAsString).withDefault([]))

page is a number. tags is an array. You did not write a parser, and you did not write the serialiser that puts them back. The URL becomes ?q=chair&page=2&tags=wood,black and survives reload, back button, bookmark and paste into a chat.

Details that stop it being annoying

const [query, setQuery] = useQueryState('q', {
  defaultValue: '',
  throttleMs: 300,   // typing does not write 40 history entries
  history: 'replace' // filter changes replace rather than stack
})

Search typing should replace history; a deliberate filter selection can push. Getting this wrong makes the back button useless, which is worse than the problem you were solving.

Defaults are omitted from the URL, so the clean state stays a clean link.

The SEO consequence, both ways

Filtered URLs are real URLs. That is an opportunity — a genuinely useful combination can be linked and indexed — and a risk, because filter permutations multiply fast and thin near-duplicate pages waste crawl budget.

Handle it explicitly: a canonical tag pointing at the unfiltered view, and noindex on combinations you do not want in the index. A small number of high-intent combinations can be allowed through deliberately. What you must not do is generate thousands of them by accident.

Where I use it

Anything with a browse experience: catalogues, dashboards with date ranges, log viewers, this site's own blog filters. The test is simple — if a colleague could plausibly want to send someone "this exact view", the state belongs in the URL.

Ephemeral UI state does not: a modal being open, a dropdown, an unsent draft. Putting those in the URL makes the back button do surprising things, which is its own kind of bug.

Resources

ReactStateSEO

Need this built properly?

I build secure, fast, bilingual platforms for clients across Egypt, Saudi Arabia, the UAE and Kuwait.

Keep Reading