类型化状态,生活在 URL 中
useUrlState 是将自身写入查询字符串的 React 状态。对象、数组和日期保持其类型,每个状态都是一个可分享的链接,并在重新加载后仍然存在——无需 provider,也无需样板代码。
- ~2 KB gzip 压缩
- 零依赖
- TypeScript 优先
- Next.js / react-router / Remix
- MIT
npm i state-in-urlstate-in-url vs nuqs
在找 nuqs 的替代品?两者都把带类型的状态存进查询字符串;区别在于需要多少配置,以及值可以是什么。
| 对比项 | state-in-url | nuqs |
|---|---|---|
| 配置 | 无需配置——导入 hook 即可使用 | 需要用适配器组件包裹应用 |
| 状态形态 | 一个带类型的对象,用法类似 React.useState | 按键存值,每个键都要声明解析器 |
| 跨组件复用 | 把 hook 包一次——所有组件共享状态,无需 props | 需要自己围绕解析器映射抽一个 hook |
| 嵌套对象和数组 | 内置支持——结构和类型都保留 | JSON 解析器加自己写的运行时校验 |
| 日期 | 自动保留 | 内置解析器,需逐键声明 |
| 体积(完整导入) | 约 2.9 KB gzip | 约 6.7 KB gzip |
| 运行时依赖 | 无 | 1 个 |
| 路由器 | Next.js、React Router v6/v7、Remix,纯 JS 辅助函数 | Next.js、React Router、Remix、TanStack Router、纯 React |
体积说明:整库导入,esbuild minify + gzip,2026 年 8 月对照 nuqs 2.10.1 测得。
nuqs 也是一个不错的库——如果你希望每个值都是一条可读的查询参数,或正在用 TanStack Router,就选它。想把整个带类型的对象放进 URL、零配置上手,就选 state-in-url。
同一个功能,两种写法
一个筛选面板:搜索文本、页码、标签列表和日期。nuqs 要为每个键声明解析器并在根部接入适配器;state-in-url 直接接收对象,并把它包成一个可复用的 hook。
// 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[]
};这个自定义 hook 就是该功能的全部 API:任何组件调用它都共享同一份带类型的状态——标签列表仍是数组,日期取回来还是真正的 Date 对象。不需要 props、context,也不用逐键接线。
配置与样板代码
nuqs 通过包裹应用的适配器组件接入路由,每份状态都要声明解析器。state-in-url 为每个路由器提供一个 hook——导入对应的那个,传入默认状态对象即可。什么都不用包。
Next.js、SSR 与预渲染
在 App Router 中,state-in-url 从不调用 useSearchParams,因此使用它的组件不需要 Suspense 边界,页面也保持可预渲染——包括 PPR。服务端组件通过原样转发的 searchParams prop 读取同一份状态。
从 nuqs 迁移
迁移通常是机械操作:把一个功能的键收进一个默认状态对象,去掉解析器声明——普通的类型化值携带同样的信息——再把逐键 setter 换成一个接收 partial 的 setter。每个顶层字段仍对应自己的查询参数。
其他选项对比
nuqs 不是唯一的替代品。同一件事——把类型化状态放进查询字符串——路由器内置能力和更老的库也能做,各有取舍。
| 库 | 配置 | 嵌套对象和日期 | 体积 | 适用场景 |
|---|---|---|---|---|
| state-in-url | 无需配置——导入 hook | 自动保留,类型完整 | 约 2.9 KB gzip,零依赖 | 在 Next.js、React Router 或 Remix 上想要零配置的类型化对象 |
| nuqs | 适配器组件,逐键解析器 | JSON 解析器加自己的校验 | 约 6.7 KB gzip,一个依赖 | 希望每个值都是一条可读查询参数 |
| TanStack Router | 每个路由的 validateSearch | 对象和数组 JSON-first;日期需自定义序列化 | 内置于路由器 | 在用 TanStack Router——用自带的 |
| use-query-params | Provider 加路由适配器,逐参数配置 | 通过 JSON 参数类型,类型松散 | 约 4.4 KB gzip 外加 serialize-query-params | 代码库已经建立在它之上 |
| useSearchParams | 无需配置——路由器内置 | 只有字符串——解析、类型和默认值全靠自己 | 0 KB | 只有一两个扁平字符串参数,不值得引库 |
常见问题
- state-in-url 是好的 nuqs 替代品吗?
- 是的,当你想把整个类型化对象放进 URL 且零配置时:没有适配器组件,没有逐键解析器,嵌套对象和日期自动保留。若你希望每个值都是一条可读查询参数,或在用 TanStack Router,nuqs 仍是更好的选择。
- state-in-url 和 nuqs 哪个更小?
- 2026 年 8 月用 esbuild(minify + gzip,整库导入)测得:state-in-url 约 2.9 KB、零运行时依赖;nuqs 2.10.1 约 6.7 KB、一个依赖。按需导入两者都会更小。
- state-in-url 需要适配器或 provider 吗?
- 不需要。每个路由器有自己的入口——导入对应的 hook,传入默认状态对象即可工作。没有包裹应用的适配器组件,也没有要配置的 context provider。
- 从 nuqs 迁移到 state-in-url 难吗?
- 通常不难:把一个功能的键收进一个默认状态对象,去掉解析器声明,把逐键 setter 换成一个接收 partial 的 setter。每个顶层字段仍对应自己的查询参数。
- TanStack Router 的 search params 呢?
- 如果你在用 TanStack Router,就用它自带的:JSON-first search params,配合每个路由的 validateSearch 校验。state-in-url 和 nuqs 的价值在于 Next.js、React Router 或 Remix——这些路由器没有内置的类型化 search params。
