État typé dans l’URL pour React et Next.js — comme useState
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, il survit aux rechargements et le bouton Précédent fonctionne — sans providers, sans frontière Suspense, sans code répétitif.
- ~2 KB en gzip
- zéro dépendance
- TypeScript-first
- Next.js / react-router / Remix / Astro
- MIT
npm i state-in-urluseUrlState — en direct avec remix.js
Tapez ci-dessous — regardez l'URL s'allumer
Lit depuis l'URL — sans props, sans context, les types et la structure sont préservés
{name: stringage: undefinedundefinedagree_to_terms: falsebooleantags: []}
Gestion d’état dans l’URL pour Next.js, React Router, Remix et Astro — la même API
Démarrage rapide
export const form: Form = {
name: '',
age: undefined,
agree_to_terms: false,
tags: [],
};
// use `Type` not `Interface`!
type Form = {
name: string;
age?: number;
agree_to_terms: boolean;
tags: { id: string; value: { text: string; time: Date } }[];
};import { useUrlState } from 'state-in-url/remix';
import { form } from './form';
// One hook per feature - the whole API for this state
export const useFormState = () => useUrlState(form);import { useFormState } from './useFormState';
export const ComponentA = () => {
// see docs for all possible params https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/remix/useUrlState
const { urlState, setUrl, setState } = useFormState();
return <>
<input
id="name"
value={urlState.name}
onChange={(ev) => setUrl({ name: ev.target.value })}
/>
// OR can update state immediately but sync change to url as needed
<input
value={urlState.name}
onChange={(ev) => { setState(curr => ({ ...curr, name: ev.target.value })) }}
onBlur={() => setUrl()}
/>
<button onClick={() => setUrl((curr, initial) => initial)}>
Reset
</button>
</>
};import { useFormState } from './useFormState';
export const ComponentB = () => {
// same state as ComponentA - no props, no context
const { urlState } = useFormState();
// will be defaultValue from `form` if not in url, no need to check
return <div>name: {urlState.name}</div>
};import React from 'react';
import { useUrlState } from 'state-in-url/remix';
import { form } from './form';
export const useFormState = () => {
const { urlState, setUrl: setUrlBase, reset } = useUrlState(form);
// first navigation will push new history entry
// all following will just replace that entry
// this way will have history with only 2 entries - ['/url', '/url?key=param']
const replace = React.useRef(false);
const setUrl = React.useCallback((
state: Parameters<typeof setUrlBase>[0],
opts?: Parameters<typeof setUrlBase>[1]
) => {
setUrlBase(state, { replace: replace.current, ...opts });
replace.current = true;
}, [setUrlBase]);
return { urlState, setUrl, resetUrl: reset };
};state-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 | Next.js, React Router v6/v7, Remix, Astro, helpers pour JS pur | 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.
Lisez la comparaison complète — la même fonctionnalité dans les deux, et comment migrer
Pourquoi state-in-url ?
Il existe des bibliothèques d'état dans l'URL, mais la plupart sont soit fastidieuses à configurer, soit limitées dans ce qu'elles peuvent stocker. state-in-url vise à être celle qui fonctionne tout simplement : une API qui reflète React.useState, avec l'URL comme stockage.
Stockez l'état sans code répétitif, construisez des liens profonds et partagez des données entre composants client sans rapport — aucun provider nécessaire. La structure et les types sont préservés de bout en bout : un Date entre, une Date sort.
Construit en test-first, avec des suites unitaires et e2e inter-navigateurs qui s'exécutent à chaque commit.
Next.js : aucune frontière Suspense
Le hook n'appelle jamais useSearchParams, donc un composant qui l'utilise n'a pas besoin d'être enveloppé dans Suspense et n'exclut pas sa page du prérendu — PPR et cacheComponents inclus. Il lit l'URL directement et suit chaque changement ultérieur, y compris un history.pushState issu d'un code qui n'en sait rien.
Pas sur Next.js ou react-router ?
Les helpers encodeState / decodeState fonctionnent avec n'importe quel framework ou du JS pur — les hooks sont une commodité par-dessus.
Consultez la page GitHub — une étoile fait beaucoup.
Partagez-le avec d'autres développeurs

