Estado tipado en la URL para React y Next.js — como useState
useUrlState es el estado de React que se escribe a sí mismo en la cadena de consulta. Los objetos, los arrays y las fechas conservan sus tipos, cada estado es un enlace compartible, sobrevive a las recargas y el botón atrás funciona — sin providers, sin límite de Suspense, sin código repetitivo.
- ~2 KB en gzip
- cero dependencias
- TypeScript-first
- Next.js / react-router / Remix / Astro
- MIT
npm i state-in-urluseUrlState — en vivo con next.js
Escribe abajo — observa cómo se enciende la URL
Lee desde la URL — sin props, sin context, los tipos y la estructura se conservan
{name: stringage: undefinedundefinedagree_to_terms: falsebooleantags: []}
Gestión de estado en la URL para Next.js, React Router, Remix y Astro — la misma API
Inicio rápido
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 } }[];
};'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 });'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>
</>
};'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>
};'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 };
};¿Usas un agente de codificación con IA?
Los agentes se equivocan siempre en las mismas dos cosas aquí. Escriben la forma del estado con interface, que la restricción JSONCompatible rechaza de plano. Y construyen el objeto de estado por defecto dentro del componente, lo que rompe el uso compartido en silencio: se indexa por identidad del objeto, así que nada da error, los dos componentes simplemente dejan de verse.
Así que el paquete incluye siete SKILL.md archivos. Tu agente carga uno bajo demanda a través de TanStack Intent, y se versionan con la biblioteca y no con esta página.
npx @tanstack/intent@latest installEjecuta una vez en un proyecto que ya tenga state-in-url instalado. Tu agente encontrará entonces las habilidades en node_modules/state-in-url/skills/.
feature-state-hookDefinir el estado y envolver useUrlState en un hook de ámbito de funcionalidadinput-handlingCampos de texto, deslizadores, cualquier cosa que cambie rápidonextjs-ssrApp Router: reenvío de searchParams, Proxy para layoutsreact-router-remix-setupConfiguración de React Router v6/v7 o Remix v2astro-setupIslas de Astro (React o Preact), o páginas sin framework en el clienteform-library-integrationCombinación con react-hook-form (o formik)shared-state-no-urluseSharedState — compartir sin tocar la URL
Las fuentes están en GitHub. Un agente que no pueda cargar las habilidades de Intent debería leer llms.txt en su lugar — las mismas reglas, condensadas en un archivo.
state-in-url vs nuqs
¿Buscas una alternativa a nuqs? Ambas guardan estado tipado en la cadena de consulta; difieren en cuánto hay que configurar y en qué puede ser un valor.
| Qué | state-in-url | nuqs |
|---|---|---|
| Configuración | Next.js, React Router v6/v7, Remix, Astro, helpers para JS puro | Un componente adaptador envuelve la app |
| Forma del estado | Un objeto tipado, como React.useState | Valores por clave, con un parser declarado para cada una |
| Reutilización entre componentes | Envuelve el hook una vez — cada componente comparte el estado, sin props | Extraes tu propio hook alrededor del mapa de parsers |
| Objetos y arrays anidados | Integrado — estructura y tipos se conservan | Parser JSON más tu propio validador |
| Fechas | Se conservan automáticamente | Parser integrado, declarado por clave |
| Tamaño, import completo | ~2,9 KB gzip | ~6,7 KB gzip |
| Dependencias en runtime | Ninguna | Una |
| Routers | Next.js, React Router v6/v7, Remix, helpers para JS puro | Next.js, React Router, Remix, TanStack Router, React puro |
Tamaños: import de toda la biblioteca, esbuild minify + gzip, medido en agosto de 2026 contra nuqs 2.10.1.
nuqs es una buena biblioteca: elígela si quieres cada valor como su propio parámetro de consulta legible o usas TanStack Router. Elige state-in-url cuando quieras un objeto tipado completo en la URL sin configuración.
Lee la comparación completa — la misma feature en ambas y cómo migrar
Estado en la URL en React — preguntas frecuentes
- ¿Por qué guardar el estado de React en la URL?
- Una URL que contiene el estado es un enlace compartible: recárgala, guárdala en marcadores o envíala y se abren los mismos filtros, la misma pestaña o la misma página. Atrás y adelante funcionan gratis, y componentes no relacionados pueden leer los mismos valores sin un provider. state-in-url lo hace con un único objeto tipado en lugar de strings parseados a mano.
- ¿Qué estado debería ir en la URL?
- Todo lo que un lector podría guardar en marcadores o compartir: filtros, ordenación, paginación, la pestaña activa, un rango de fechas, el texto de búsqueda. Deja fuera lo privado, lo enorme o lo puramente transitorio: tokens de autenticación, si un diálogo está abierto, la posición del ratón. Una prueba rápida: ¿tendría sentido un enlace compartido con este valor dentro?
- ¿Cómo leo y escribo parámetros de la URL en React con state-in-url?
- Llama a useUrlState con un objeto de estado por defecto. urlState contiene los valores actuales, ya tipados; setUrl escribe un objeto parcial en la cadena de consulta; setState actualiza el estado sin tocar la URL hasta que lo vuelques. Números, booleanos, arrays, objetos anidados y Dates vuelven con los mismos tipos con los que entraron.
- ¿Sobrevive el estado en la URL a una recarga de la página?
- Sí. El estado es la cadena de consulta, así que una recarga, un marcador o un enlace pegado en otro sitio lo restauran. En el App Router de Next.js, pasa la prop searchParams de la página al hook para que el primer render en el servidor ya muestre los valores correctos en lugar de los valores por defecto.
- ¿Funciona con Server Components de Next.js, sin límite de Suspense?
- Sí. El hook nunca llama a useSearchParams, así que un componente que lo usa no necesita límite de Suspense y no excluye la página del prerenderizado, PPR incluido. Los Server Components leen el mismo estado a través de la prop searchParams; un layout puede decodificarlo desde una cabecera establecida en proxy.ts.
- ¿Puedo sincronizar react-hook-form o una biblioteca de tablas con la URL?
- Sí. Mantén la biblioteca de formularios como fuente de verdad, inicialízala con urlState como valores por defecto y refleja sus cambios con setUrl desde un manejador de cambios o un efecto. El mismo patrón sirve para el estado de TanStack Table, paneles de filtros y cualquier otra cosa que exponga valores y un setter.
- ¿Qué frameworks soporta state-in-url?
- Next.js 14-16 App Router, React Router v6 y v7, Remix v2 e islas de Astro (React o Preact), cada uno con su propio entry point. JavaScript puro y cualquier otro framework pueden usar directamente los helpers encodeState y decodeState. Pesa ~2 KB en gzip con cero dependencias.
¿Por qué state-in-url?
Existen bibliotecas de estado en la URL, pero la mayoría son engorrosas de configurar o limitadas en lo que pueden almacenar. state-in-url aspira a ser la que simplemente funciona: una API que imita React.useState, con la URL como almacén.
Guarda estado sin código repetitivo, construye enlaces profundos y comparte datos entre componentes de cliente no relacionados, sin necesidad de provider. La estructura y los tipos se conservan de extremo a extremo: un Date entra, un Date sale.
Construido con test-first, con suites unitarias y e2e entre navegadores ejecutándose en cada commit.
Next.js: sin límite de Suspense
El hook nunca llama a useSearchParams, por lo que un componente que lo usa no necesita envolverse en Suspense y no excluye su página del prerenderizado: PPR y cacheComponents incluidos. Lee la URL directamente y sigue cada cambio posterior, incluido un history.pushState desde código que no sabe nada de él.
¿No usas Next.js o react-router?
Los helpers encodeState / decodeState funcionan con cualquier framework o JS puro: los hooks son una comodidad encima.
Échale un vistazo a la página de GitHub : una estrella ayuda mucho.
Compártelo con otros desarrolladores

