타입이 지정된 상태, 사는 곳은 URL
useUrlState 는 자신을 쿼리 문자열에 기록하는 React 상태입니다. 객체, 배열, 날짜는 타입을 유지하고, 모든 상태는 공유 가능한 링크가 되며, 새로고침 후에도 유지됩니다. 프로바이더도 보일러플레이트도 필요 없습니다.
- ~2 KB gzip 압축
- 의존성 없음
- TypeScript 우선
- Next.js / react-router / Remix
- MIT
npm i state-in-urluseUrlState — 라이브로 시연: react-router
아래에 입력하세요 — URL이 빛나는 것을 보세요
URL에서 읽습니다 — props도 context도 없이, 타입과 구조가 유지됩니다
{name: stringage: undefinedundefinedagree_to_terms: falsebooleantags: []}
같은 API, 세 가지 라우터
빠른 시작
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 } }[];
};import { useUrlState } from 'state-in-url/react-router';
// for react-router v6
// import { useUrlState } from 'state-in-url/react-router6';
import { form } from './form';
// One hook per feature - the whole API for this state
export const useFormState = () => useUrlState(form);import { useFormState } from './useFormState';
export const ComponentA = () => {
// see docs for all possible params https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState
const { urlState, setUrl, setState } = 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>
</>
};import { useFormState } from './useFormState';
export const ComponentB = () => {
// same state as ComponentA - no props, no context
const { urlState } = useFormState();
// will be defaultValue from `form` if not in url, no need to check
return <div>name: {urlState.name}</div>
};import React from 'react';
import { useUrlState } from 'state-in-url/react-router';
import { form } from './form';
export const useFormState = () => {
const { urlState, setUrl: setUrlBase, reset } = useUrlState(form);
// 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 };
};state-in-url vs nuqs
nuqs 대안을 찾고 계신가요? 둘 다 타입이 유지되는 상태를 쿼리 문자열에 저장하지만, 필요한 설정량과 값으로 담을 수 있는 것이 다릅니다.
| 항목 | state-in-url | nuqs |
|---|---|---|
| 설정 | 불필요 — hook을 import하면 끝 | 어댑터 컴포넌트로 앱을 감싸야 함 |
| 상태 형태 | React.useState 같은 타입 객체 하나 | 키별 값, 각 키마다 파서 선언 |
| 컴포넌트 간 재사용 | hook을 한 번 감싸면 — 모든 컴포넌트가 상태 공유, props 불필요 | 파서 맵을 감싸는 hook은 직접 추출 |
| 중첩 객체와 배열 | 기본 지원 — 구조와 타입 유지 | JSON 파서에 자체 런타임 검증 추가 필요 |
| 날짜 | 자동으로 유지 | 내장 파서를 키마다 선언 |
| 크기, 전체 import | 약 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 |
크기: 라이브러리 전체 import, esbuild minify + gzip, 2026년 8월 nuqs 2.10.1 기준 측정.
nuqs도 훌륭한 라이브러리입니다. 값마다 읽기 쉬운 쿼리 파라미터를 원하거나 TanStack Router를 쓴다면 nuqs를, 타입 객체 전체를 설정 없이 URL에 넣고 싶다면 state-in-url을 선택하세요.
왜 state-in-url인가?
URL 상태 라이브러리는 존재하지만, 대부분은 설정이 번거롭거나 저장할 수 있는 것에 제한이 있습니다. state-in-url 은(는) 그냥 동작하는 것을 목표로 합니다. React.useState 을(를) 그대로 반영한 API로, URL을 저장소로 사용합니다.
보일러플레이트 없이 상태를 저장하고, 딥 링크를 만들고, 서로 관련 없는 클라이언트 컴포넌트 간에 데이터를 공유하세요. 프로바이더가 필요 없습니다. 구조와 타입은 처음부터 끝까지 유지됩니다. Date 이 들어가면 Date 이 나옵니다.
테스트 우선으로 구축되었으며, 유닛 테스트와 크로스 브라우저 e2e 스위트가 모든 커밋에서 실행됩니다.
Next.js: Suspense 경계 불필요
이 훅은 useSearchParams 을(를) 호출하지 않으므로, 이를 사용하는 컴포넌트는 Suspense 로 감쌀 필요가 없고, 페이지가 사전 렌더링에서 제외되지도 않습니다. PPR과 cacheComponents 도 포함됩니다. URL을 직접 읽고 이후의 모든 변경을 따라갑니다. 예를 들어 history.pushState 을(를), 그 존재조차 모르는 코드에서 호출한 경우도 따라갑니다.
Next.js나 react-router를 사용하지 않으시나요?
이 encodeState / decodeState 헬퍼는 어떤 프레임워크나 순수 JS에서도 동작합니다. 훅은 그 위의 편의 레이어입니다.
다음 GitHub 페이지 를 확인해 보세요. 스타 하나가 큰 힘이 됩니다.
다른 개발자와 공유하기

