État typé, vivant dans l'URL
useUrlState est l'état React qui s'écrit lui-même dans la chaîne de requête. Les objets, les tableaux et les dates conservent leurs types, chaque état est un lien partageable et survit aux rechargements — sans providers, sans code répétitif.
- ~2 KB en gzip
- zéro dépendance
- TypeScript-first
- Next.js / react-router / Remix
- MIT
npm i state-in-urlstate-in-url vs nuqs
Vous cherchez une alternative à nuqs ? Les deux gardent un état typé dans la query string ; elles diffèrent par la configuration requise et par ce qu’une valeur peut être.
| Quoi | state-in-url | nuqs |
|---|---|---|
| Mise en place | Aucune — importez le hook et c’est parti | Un composant adaptateur enveloppe l’application |
| Forme de l’état | Un objet typé, comme React.useState | Valeurs par clé, un parseur déclaré pour chacune |
| Réutilisation entre composants | Enveloppez le hook une fois — chaque composant partage l’état, sans props | Vous extrayez votre propre hook autour de la table de parseurs |
| Objets et tableaux imbriqués | Intégré — structure et types préservés | Parseur JSON plus votre propre validateur |
| Dates | Préservées automatiquement | Parseur intégré, déclaré par clé |
| Taille, import complet | ~2,9 Ko gzip | ~6,7 Ko gzip |
| Dépendances au runtime | Aucune | Une |
| Routeurs | Next.js, React Router v6/v7, Remix, helpers pour JS pur | Next.js, React Router, Remix, TanStack Router, React pur |
Tailles : import de la bibliothèque entière, esbuild minify + gzip, mesuré en août 2026 face à nuqs 2.10.1.
nuqs est une bonne bibliothèque — choisissez-la pour un query param lisible par valeur, ou si vous êtes sur TanStack Router. Choisissez state-in-url pour un objet typé complet dans l’URL, sans configuration.
La même fonctionnalité, dans les deux
Un panneau de filtres : un texte de recherche, un numéro de page, une liste de tags et une date. nuqs déclare un parseur par clé et branche un adaptateur à la racine ; state-in-url prend l’objet et l’enveloppe dans un hook réutilisable.
// app/layout.tsx
import { NuqsAdapter } from 'nuqs/adapters/next/app';
export default function RootLayout({ children }) {
return (
<html>
<body>
<NuqsAdapter>{children}</NuqsAdapter>
</body>
</html>
);
}'use client';
import {
useQueryStates,
parseAsString,
parseAsInteger,
parseAsArrayOf,
parseAsIsoDateTime,
} from 'nuqs';
export const Filters = () => {
const [filters, setFilters] = useQueryStates({
q: parseAsString.withDefault(''),
page: parseAsInteger.withDefault(1),
tags: parseAsArrayOf(parseAsString).withDefault([]),
since: parseAsIsoDateTime,
});
return (
<input
value={filters.q}
onChange={(ev) => setFilters({ q: ev.target.value, page: 1 })}
/>
);
};'use client';
import { useUrlState } from 'state-in-url/next';
export const filters = {
q: '',
page: 1,
tags: [] as string[],
since: undefined as Date | undefined,
};
// One reusable hook = the whole API for this feature
export const useFilters = () => useUrlState(filters);
export const SearchBox = () => {
const { urlState, setUrl } = useFilters();
return (
<input
value={urlState.q}
onChange={(ev) => setUrl({ q: ev.target.value, page: 1 })}
/>
);
};
export const ActiveTags = () => {
// Same state, another component - no props, no context
const { urlState } = useFilters();
return <>{urlState.tags.join(', ')}</>; // tags is still string[]
};Ce seul hook personnalisé est toute l’API de la fonctionnalité : chaque composant qui l’appelle partage le même état typé — la liste de tags reste un tableau, la date revient en véritable objet Date. Pas de props, pas de context, pas de câblage par clé.
Mise en place et boilerplate
nuqs se branche sur votre routeur via un composant adaptateur enveloppant l’application, et chaque morceau d’état déclare son parseur. state-in-url fournit un hook par routeur — importez celui qui correspond, passez-lui un objet d’état par défaut, terminé. Rien n’enveloppe rien.
Next.js, SSR et prérendu
Sur l’App Router, state-in-url n’appelle jamais useSearchParams, donc les composants qui l’utilisent n’ont pas besoin d’une frontière Suspense et leurs pages restent prérendues — PPR compris. Les composants serveur lisent le même état via la prop searchParams, transmise telle quelle.
Migrer depuis nuqs
La migration est le plus souvent mécanique : rassemblez les clés d’une fonctionnalité dans un seul objet d’état par défaut, supprimez les déclarations de parseurs — des valeurs typées portent la même information — et remplacez les setters par clé par un seul setter acceptant un partiel. Chaque champ de premier niveau reste son propre paramètre de query.
Ce que valent les autres options
nuqs n’est pas la seule alternative. Le même besoin — un état typé dans la query string — est aussi couvert par les routeurs eux-mêmes et des bibliothèques plus anciennes, chacune avec son compromis.
| Bibliothèque | Mise en place | Objets imbriqués et dates | Taille | À choisir quand |
|---|---|---|---|---|
| state-in-url | Aucune — importez le hook | Préservés automatiquement, types compris | ~2,9 Ko gzip, zéro dépendance | Vous voulez un objet typé sans configuration sur Next.js, React Router ou Remix |
| nuqs | Composant adaptateur, parseur par clé | Parseur JSON plus votre propre validateur | ~6,7 Ko gzip, une dépendance | Vous voulez chaque valeur comme query param lisible |
| TanStack Router | validateSearch sur chaque route | JSON-first pour objets et tableaux ; dates via une sérialisation maison | Intégré au routeur | Vous êtes sur TanStack Router — utilisez ce qu’il fournit |
| use-query-params | Provider plus adaptateur de routeur, config par paramètre | Via un type de paramètre JSON, faiblement typé | ~4,4 Ko gzip plus serialize-query-params | Une base de code déjà construite dessus |
| useSearchParams | Aucune — intégré au routeur | Chaînes uniquement — parsing, types et défauts à votre charge | 0 Ko | Un ou deux paramètres plats, pas besoin de bibliothèque |
Questions fréquentes
- state-in-url est-il une bonne alternative à nuqs ?
- Oui, quand vous voulez un objet typé complet dans l’URL sans configuration : pas de composant adaptateur, pas de parseurs par clé, objets imbriqués et dates préservés automatiquement. nuqs reste le meilleur choix pour un query param lisible par valeur, ou si vous êtes sur TanStack Router.
- Lequel est le plus léger, state-in-url ou nuqs ?
- Mesuré avec esbuild (minify + gzip, import de la bibliothèque entière) en août 2026 : state-in-url pèse ~2,9 Ko sans dépendance ; nuqs 2.10.1 pèse ~6,7 Ko avec une dépendance. Importer un sous-ensemble réduit les deux.
- state-in-url a-t-il besoin d’un adaptateur ou d’un provider ?
- Non. Chaque routeur a son point d’entrée — importez le hook correspondant, passez-lui un objet d’état par défaut, et ça fonctionne. Aucun composant adaptateur autour de l’application, aucun provider de contexte à configurer.
- Est-il difficile de migrer de nuqs vers state-in-url ?
- Le plus souvent non : rassemblez les clés d’une fonctionnalité dans un seul objet d’état par défaut, supprimez les déclarations de parseurs et remplacez les setters par clé par un seul setter acceptant un partiel. Chaque champ de premier niveau reste son propre paramètre de query.
- Et les search params de TanStack Router ?
- Si vous êtes sur TanStack Router, utilisez ce qu’il fournit : des search params JSON-first validés par route avec validateSearch. state-in-url et nuqs comptent quand votre routeur est Next.js, React Router ou Remix, sans search params typés intégrés.
