State URL có kiểu cho React & Next.js — như useState

useUrlState là state React tự ghi chính nó vào chuỗi truy vấn. Object, array và ngày giữ nguyên kiểu, mọi state là một liên kết có thể chia sẻ, tồn tại qua các lần tải lại và nút quay lại hoạt động — không provider, không ranh giới Suspense, không boilerplate.

  • ~2 KB nén gzip
  • không phụ thuộc
  • TypeScript-first
  • Next.js / react-router / Remix / Astro
  • MIT
npm i state-in-url

useUrlState — trực tiếp với next.js

Gõ bên dưới — xem URL sáng lên

Client component đầu tiên
Client component khác

Đọc từ URL — không props, không context, kiểu và cấu trúc được giữ nguyên

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

Quản lý state URL cho Next.js, React Router, Remix và Astro — cùng một API

Bắt đầu nhanh

1. Định nghĩa state
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. Bọc nó trong một hook tái sử dụng
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. Dùng trong bất kỳ component nào — cùng chia sẻ state
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. Mở rộng hook khi cần thêm
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 };
};

Đang dùng AI coding agent?

Các agent mắc cùng hai lỗi ở đây, mọi lúc. Chúng định nghĩa hình dạng state bằng interface, mà ràng buộc JSONCompatible từ chối ngay lập tức. Và chúng xây dựng object state mặc định bên trong component, điều này ngầm phá vỡ việc chia sẻ — nó được khóa theo định danh object, nên không có lỗi, hai component chỉ đơn giản ngừng nhìn thấy nhau.

Vậy nên package kèm theo bảy SKILL.md file. Agent của bạn tải một file theo yêu cầu qua TanStack Intent, và chúng được quản lý phiên bản cùng thư viện chứ không phải cùng trang này.

npx @tanstack/intent@latest install

Chạy một lần trong dự án đã cài state-in-url . Sau đó agent của bạn tìm các skill trong node_modules/state-in-url/skills/.

  • feature-state-hookĐịnh nghĩa state, và bọc useUrlState trong một hook theo phạm vi tính năng
  • input-handlingÔ nhập văn bản, thanh trượt, bất cứ thứ gì thay đổi nhanh
  • nextjs-ssrApp Router: chuyển tiếp searchParams, Proxy cho layout
  • react-router-remix-setupThiết lập React Router v6/v7 hoặc Remix v2
  • astro-setupIsland của Astro (React hoặc Preact), hoặc trang không có framework phía client
  • form-library-integrationKết hợp với react-hook-form (hoặc formik)
  • shared-state-no-urluseSharedState — chia sẻ mà không chạm vào URL

Nguồn ở trên GitHub. Agent không thể tải các skill Intent nên đọc llms.txt thay thế — cùng các quy tắc, cô đọng trong một file.

state-in-url vs nuqs

Đang tìm một lựa chọn thay thế nuqs? Cả hai đều lưu state có kiểu trong query string; khác nhau ở lượng cấu hình cần thiết và giá trị có thể là gì.

Tiêu chístate-in-urlnuqs
Cài đặtNext.js, React Router v6/v7, Remix, Astro, helper cho JS thuầnComponent adapter bọc quanh ứng dụng
Hình dạng stateMột object có kiểu, giống React.useStateGiá trị theo từng khóa, mỗi khóa khai báo một parser
Tái sử dụng giữa các componentBọc hook một lần — mọi component chia sẻ state, không cần propsBạn tự tách hook quanh bộ parser
Object và mảng lồng nhauCó sẵn — giữ nguyên cấu trúc và kiểuParser JSON cộng thêm validator tự viết
Ngày thángGiữ nguyên tự độngParser có sẵn, khai báo theo từng khóa
Kích thước, import toàn bộ~2.9 KB gzip~6.7 KB gzip
Phụ thuộc runtimeKhôngMột
RouterNext.js, React Router v6/v7, Remix, helper cho JS thuầnNext.js, React Router, Remix, TanStack Router, React thuần

Kích thước: import cả thư viện, esbuild minify + gzip, đo tháng 8/2026 với nuqs 2.10.1.

nuqs là một thư viện tốt — hãy chọn nó khi bạn muốn mỗi giá trị là một query param dễ đọc, hoặc đang dùng TanStack Router. Chọn state-in-url khi bạn muốn cả một object có kiểu nằm trong URL mà không cần cấu hình.

Đọc bản so sánh đầy đủ — cùng một tính năng viết bằng cả hai, và cách di chuyển

State URL trong React — câu hỏi thường gặp

Tại sao nên giữ state React trong URL?
URL chứa state là một liên kết có thể chia sẻ: tải lại, đánh dấu trang hoặc gửi đi thì cùng bộ lọc, tab hay trang đó sẽ mở ra. Nút quay lại và tiến tới tự động hoạt động, và các component không liên quan có thể đọc cùng giá trị mà không cần provider. state-in-url làm việc này với một object có kiểu thay vì các chuỗi tự parse.
State nào nên nằm trong URL?
Bất cứ thứ gì người đọc có thể đánh dấu hoặc chia sẻ: bộ lọc, sắp xếp, phân trang, tab đang mở, khoảng ngày, văn bản tìm kiếm. Tránh những gì riêng tư, quá lớn hoặc thuần tạm thời — token xác thực, dialog có đang mở hay không, vị trí chuột. Một phép thử nhanh: liên kết được chia sẻ có còn ý nghĩa với giá trị này trong đó không?
Đọc và ghi tham số URL trong React bằng state-in-url như thế nào?
Gọi useUrlState với một object state mặc định. urlState giữ giá trị hiện tại, đã có kiểu; setUrl ghi một object partial vào chuỗi truy vấn; setState cập nhật state mà không chạm vào URL cho đến khi bạn flush nó. Số, boolean, mảng, object lồng nhau và Date trả về đúng kiểu như lúc đưa vào.
State URL có tồn tại sau khi tải lại trang không?
Có. State chính là chuỗi truy vấn, nên tải lại, đánh dấu trang hay dán liên kết ở nơi khác đều khôi phục nó. Trên Next.js App Router, truyền prop searchParams của trang vào hook để lần render đầu trên server đã hiển thị đúng giá trị thay vì giá trị mặc định.
Có hoạt động với Next.js Server Components mà không cần ranh giới Suspense không?
Có. Hook không bao giờ gọi useSearchParams, nên component dùng nó không cần ranh giới Suspense và không khiến trang bị loại khỏi prerendering, kể cả PPR. Server Components đọc cùng state qua prop searchParams; layout có thể giải mã nó từ một header đặt trong proxy.ts.
Có thể đồng bộ react-hook-form hoặc thư viện bảng với URL không?
Có. Giữ thư viện form làm nguồn sự thật, khởi tạo nó với urlState làm giá trị mặc định, và phản chiếu thay đổi của nó bằng setUrl từ một handler thay đổi hoặc một effect. Cùng mẫu này áp dụng cho state của TanStack Table, panel bộ lọc và bất cứ thứ gì cung cấp giá trị cùng một setter.
state-in-url hỗ trợ những framework nào?
Next.js 14-16 App Router, React Router v6 và v7, Remix v2 và island của Astro (React hoặc Preact), mỗi cái qua entry point riêng. JavaScript thuần và bất kỳ framework nào khác có thể dùng trực tiếp các helper encodeState và decodeState. Thư viện nặng ~2 KB nén gzip, không phụ thuộc.

Tại sao là state-in-url?

Các thư viện state trong URL đã tồn tại, nhưng hầu hết hoặc rườm rà khi thiết lập, hoặc hạn chế về thứ có thể lưu. state-in-url hướng tới việc đơn giản là hoạt động: một API phản chiếu React.useState, với URL làm nơi lưu trữ.

Lưu state không cần boilerplate, xây deep link và chia sẻ dữ liệu giữa các client component không liên quan — không cần provider. Cấu trúc và kiểu được giữ nguyên từ đầu đến cuối: một Date vào, một Date ra.

Được xây dựng theo test-first, với bộ test đơn vị và e2e đa trình duyệt chạy trên mỗi commit.

Next.js: không cần ranh giới Suspense

Hook không bao giờ gọi useSearchParams, nên component dùng nó không cần bọc trong Suspense và không khiến trang bị loại khỏi prerendering — bao gồm PPR và cacheComponents . Nó đọc URL trực tiếp và theo dõi mọi thay đổi sau đó, bao gồm cả history.pushState từ code không biết gì về nó.

Hướng dẫn Next.js

Không dùng Next.js hoặc react-router?

Các helper encodeState / decodeState hoạt động với bất kỳ framework hoặc JS thuần — các hook là sự tiện lợi bên trên.

Hãy xem trang GitHub — một star giúp ích rất nhiều.

Chia sẻ với các nhà phát triển khác

Uneed Embed Badge