类型化状态,生活在 URL 中

useUrlState 是将自身写入查询字符串的 React 状态。对象、数组和日期保持其类型,每个状态都是一个可分享的链接,并在重新加载后仍然存在——无需 provider,也无需样板代码。

  • ~2 KB gzip 压缩
  • 零依赖
  • TypeScript 优先
  • Next.js / react-router / Remix
  • MIT
npm i state-in-url

state-in-url vs nuqs

在找 nuqs 的替代品?两者都把带类型的状态存进查询字符串;区别在于需要多少配置,以及值可以是什么。

对比项state-in-urlnuqs
配置无需配置——导入 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 (nuqs)
// app/layout.tsx
import { NuqsAdapter } from 'nuqs/adapters/next/app';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <NuqsAdapter>{children}</NuqsAdapter>
      </body>
    </html>
  );
}
filters.tsx (nuqs)
'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 })}
    />
  );
};
filters.tsx (state-in-url)
'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-paramsProvider 加路由适配器,逐参数配置通过 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。