É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-url

useUrlState — en direct avec next.js

Tapez ci-dessous — regardez l'URL s'allumer

Premier composant client
Autre composant client

Lit depuis l'URL — sans props, sans context, les types et la structure sont préservés

{
name: string
age: undefinedundefined
agree_to_terms: falseboolean
tags: []
}

Gestion d’état dans l’URL pour Next.js, React Router, Remix et Astro — la même API

Démarrage rapide

1. Définissez l’état
state
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 } }[];
};
2. Enveloppez-le dans un hook réutilisable
useFormState
'use client';

import { useUrlState } from 'state-in-url/next';
import { form } from './form';

// One hook per feature - the whole API for this state
// "searchParams" only needed to pass params from Server Components
export const useFormState = (searchParams?: object) =>
  useUrlState(form, { searchParams });
3. Utilisez-le dans n’importe quel composant — tous le partagent
ComponentA
'use client';

import { useFormState } from './useFormState';

export const ComponentA = () => {
  // see docs for all possible params https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState
  const { urlState, setState, setUrl } = 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>
    </>
};
ComponentB
'use client';
import { useFormState } from './useFormState';

// "searchParams" used to pass params from Server Components
export const ComponentB = ({ searchParams }: { searchParams?: object }) => {
  // same state as ComponentA - no props, no context
  const { urlState } = useFormState(searchParams);

// will be defaultValue from `form` if not in url, no need to check

  return <div>name: {urlState.name}</div>
};
4. Faites grandir le hook quand il en faut plus
useFormState - extended
'use client';

import React from 'react';
import { useUrlState } from 'state-in-url/next';
import { form } from './form';

export const useFormState = ({ searchParams }: { searchParams?: object }) => {
  const { urlState, setUrl: setUrlBase, reset } = useUrlState(form, {
    searchParams,
  });

  // 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 };
};

Vous utilisez un agent de codage IA ?

Les agents se trompent toujours sur les deux mêmes choses ici. Ils définissent la forme de l'état avec interface, que la contrainte JSONCompatible rejette d'emblée. Et ils construisent l'objet d'état par défaut dans le composant, ce qui casse le partage en silence — il est indexé par l'identité de l'objet, donc rien ne génère d'erreur, les deux composants cessent simplement de se voir.

Le paquet fournit donc sept SKILL.md fichiers. Votre agent en charge un à la demande via TanStack Intent, et ils sont versionnés avec la bibliothèque plutôt qu'avec cette page.

npx @tanstack/intent@latest install

Exécutez une fois dans un projet qui a déjà state-in-url installé. Votre agent trouve alors les compétences dans node_modules/state-in-url/skills/.

  • feature-state-hookDéfinir l'état et envelopper useUrlState dans un hook à portée de fonctionnalité
  • input-handlingChamps de texte, curseurs, tout ce qui change vite
  • nextjs-ssrApp Router : transfert de searchParams, Proxy pour les layouts
  • react-router-remix-setupConfiguration de React Router v6/v7 ou Remix v2
  • astro-setupÎlots Astro (React ou Preact), ou pages sans framework côté client
  • form-library-integrationAssociation avec react-hook-form (ou formik)
  • shared-state-no-urluseSharedState — partager sans toucher à l'URL

Les sources sont sur GitHub. Un agent qui ne peut pas charger les compétences d'Intent devrait lire llms.txt à la place — les mêmes règles, condensées en un seul fichier.

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.

Quoistate-in-urlnuqs
Mise en placeNext.js, React Router v6/v7, Remix, Astro, helpers pour JS purUn composant adaptateur enveloppe l’application
Forme de l’étatUn objet typé, comme React.useStateValeurs par clé, un parseur déclaré pour chacune
Réutilisation entre composantsEnveloppez le hook une fois — chaque composant partage l’état, sans propsVous extrayez votre propre hook autour de la table de parseurs
Objets et tableaux imbriquésIntégré — structure et types préservésParseur JSON plus votre propre validateur
DatesPréservées automatiquementParseur intégré, déclaré par clé
Taille, import complet~2,9 Ko gzip~6,7 Ko gzip
Dépendances au runtimeAucuneUne
RouteursNext.js, React Router v6/v7, Remix, helpers pour JS purNext.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

État dans l’URL en React — questions fréquentes

Pourquoi garder l’état React dans l’URL ?
Une URL qui porte l’état est un lien partageable : rechargez-la, mettez-la en favori ou envoyez-la, et les mêmes filtres, le même onglet ou la même page s’ouvrent. Précédent et Suivant fonctionnent sans rien faire, et des composants sans rapport lisent les mêmes valeurs sans provider. state-in-url le fait avec un seul objet typé plutôt qu’avec des chaînes parsées à la main.
Quel état a sa place dans l’URL ?
Tout ce qu’un lecteur pourrait mettre en favori ou partager : filtres, tri, pagination, onglet actif, plage de dates, texte de recherche. Laissez de côté ce qui est privé, énorme ou purement transitoire — jetons d’authentification, ouverture d’une boîte de dialogue, position de la souris. Un test rapide : un lien partagé aurait-il encore un sens avec cette valeur dedans ?
Comment lire et écrire des paramètres d’URL en React avec state-in-url ?
Appelez useUrlState avec un objet d’état par défaut. urlState contient les valeurs courantes, déjà typées ; setUrl écrit un objet partiel dans la chaîne de requête ; setState met l’état à jour sans toucher à l’URL tant que vous ne l’y écrivez pas. Nombres, booléens, tableaux, objets imbriqués et Dates reviennent avec les types qu’ils avaient en entrant.
L’état dans l’URL survit-il à un rechargement de page ?
Oui. L’état est la chaîne de requête, donc un rechargement, un favori ou un lien collé ailleurs le restaure. Sur l’App Router de Next.js, passez la prop searchParams de la page au hook pour que le premier rendu serveur affiche déjà les bonnes valeurs plutôt que les valeurs par défaut.
Fonctionne-t-il avec les Server Components de Next.js, sans frontière Suspense ?
Oui. Le hook n’appelle jamais useSearchParams, donc un composant qui l’utilise n’a pas besoin de frontière Suspense et n’exclut pas la page du prérendu, PPR compris. Les Server Components lisent le même état via la prop searchParams ; un layout peut le décoder depuis un en-tête défini dans proxy.ts.
Puis-je synchroniser react-hook-form ou une bibliothèque de tableaux avec l’URL ?
Oui. Gardez la bibliothèque de formulaires comme source de vérité, initialisez-la avec urlState comme valeurs par défaut, et reflétez ses changements avec setUrl depuis un gestionnaire de changement ou un effet. Le même schéma fonctionne pour l’état de TanStack Table, les panneaux de filtres et tout ce qui expose des valeurs et un setter.
Quels frameworks state-in-url prend-il en charge ?
Next.js 14 à 16 avec l’App Router, React Router v6 et v7, Remix v2 et les îlots Astro (React ou Preact), chacun via son propre point d’entrée. Le JavaScript pur et tout autre framework peuvent utiliser directement les helpers encodeState et decodeState. La bibliothèque pèse ~2 KB en gzip, sans dépendance.

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.

Guide Next.js

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

Uneed Embed Badge