처음 만난 Next.js 강의 출시 기념! 얼리버드 할인 중 🎉
URL을 통한 상태 관리 라이브러리
2025-06-01
43
7분
soaple
안녕하세요, 소플입니다.
웹 애플리케이션을 개발하다보면 수많은 상태들을 사용하게 됩니다.
리액트에서는 일반적으로 useState()
훅을 사용해서 각 컴포넌트 내에 상태들을 저장하고 업데이트 합니다.
하지만 이렇게 저장된 상태 값들은 브라우저를 새로고침 하는 순간 모두 초기화 됩니다.
그래서 동일한 주소에 대해서 항상 동일한 결과를 보장하기 위해서는 다른 방식으로 상태를 저장해야 합니다.
그 중에 가장 대표적인 방식이 바로 URL에 상태를 저장하는 URL State라는 방식입니다.
이번 매거진에서는 URL State와 그를 위한 nuqs라는 라이브러리에 대해 살펴보도록 하겠습니다.
URL State는 웹 애플리케이션에서 URL에 애플리케이션의 현재 상태를 표현하거나 저장하는 방식을 의미합니다.
일반적으로는 URL을 단순히 페이지의 주소로만 사용하고, 상태들은 컴포넌트 내에서 관리하거나 상태관리 라이브러리 등을 사용해서 관리하는 경우가 많습니다.
예를 들어, 게시글 목록을 보여주기 위한 경로를 아래와 같이 구성하고, 각각의 상태들을 useState()
훅으로 관리하는 것이죠.
/posts
page
)limit
)sortOrder
)하지만 이러한 방식은 페이지가 처음부터 다시 로드되는 경우 상태가 보존되지 않는다는 특징이 있습니다.
브라우저를 새로고침 하거나, 내가 보고 있는 페이지를 공유하기 위해서 주소를 복사해서 메시지로 보내는 경우가 이에 해당됩니다.
이런 상황을 방지하기 위해 페이지의 상태들을 URL에 저장하고,
페이지가 다시 로드되더라도 동일한 상태로 복원할 수 있게 해주는 것이 바로 URL State 방식입니다.
위 예시를 URL State로 표현하면 아래와 같이 되며, 각각의 URL이 고유한 상태 값을 담고 있습니다.
/posts?page=1&limit=20&sortOrder=created
/posts?page=3&limit=10&sortOrder=hits
정리해보면, URL State는 특정 페이지를 표시하기 위한 상태들을 URL에 담고, 이를 통해 페이지를 동일하게 복원시키는 방식입니다.
URL State의 주요 구성 요소는 다음과 같습니다.
먼저 URL을 구성하는 필수적인 요소인 페이지의 경로(Path) 가 있습니다.
영어로 Route 또는 Path라고 부르는 경로는, 어떤 페이지를 표시할지를 나타냅니다.
예를 들면, /
는 첫 페이지인 인덱스 페이지의 경로이고, /posts
는 게시글 목록 페이지의 경로입니다.
그리고 동적인 파라미터를 사용하는 /posts/123
은 ID가 123
인 게시글의 페이지를 나타냅니다.
다음으로 URL을 구성하는 요소로는 쿼리 파라미터(Query Parameters) 가 있습니다.
일반적으로 서치 파라미터(Search Parameters) 또는 쿼리 파라미터(Query Parameters)라고 부르며,
URL에서 필수적인 요소가 아닌 선택적인 요소입니다.
쿼리 파라미터는 경로 뒤에 물음표(?
)를 작성하고, 뒤에 key=value
형태의 키와 값의 쌍을 &
로 묶어서 작성합니다.
이를 통해 페이지에 필요한 추가적인 상태 정보를 전달합니다.
앞에서 살펴본 /posts?page=1&limit=20&sortOrder=created
가 쿼리 파라미터를 사용한 경우입니다.
URL을 구성하는 마지막 요소로는 해시 (Hash) 가 있습니다.
해시는 특정 element에 있는 id
속성을 통해 곧바로 해당 element로 탐색을 할 수 있게 해주며,
쿼리 파라미터와 마찬가지로 URL에서 필수적인 요소가 아닌 선택적인 요소입니다.
앵커(Anchor)라고 부르기도 하며, URL의 제일 끝에 샵(#
)을 사용해서 표기합니다.
예를 들면, /products#electronics
는 전자제품 섹션으로 이동하거나 해당 상태를 나타냅니다.
이처럼 URL에는 경로, 쿼리 파라미터, 해시 같은 다양한 구성요소가 포함되어 있으며, 이를 통해 다양한 상태들을 담아서 사용할 수 있습니다.
아래 URL은 경로, 쿼리 파라미터, 해시를 모두 사용한 예시입니다.
https://myshopping.com/shop/electronics?sort=price&category=laptops#specifications
myshopping.com
이라는 쇼핑몰의 전자제품 페이지(/shop/electronics
)에서,
랩탑 카테고리를 가격순으로 정렬(?sort=price&category=laptops
)하고,
상세 사양(#specifications
) 섹션을 나타내는 URL입니다.
URL State를 사용하면 브라우저의 뒤로 가기/앞으로 가기 버튼을 통해 이전 상태로 복원할 수도 있고,
URL을 복사해서 동일한 상태의 페이지를 다른 사용자와 공유하거나 북마크로 저장할 수도 있습니다.
아래는 URL State의 대표적인 사용 사례입니다.
인터넷 쇼핑몰에는 수많은 상품들이 존재하고, 사용자가 카테고리, 가격 범위, 정렬 순서 등을 선택하여 필터링 할 수 있습니다.
이러한 필터링 기능을 구현할 때, 사용자가 선택한 필터링 조건을 URL에 저장하는 형태로 많이 구현합니다.
이렇게 하게 되면, 페이지를 새로고침하거나 공유했을 때 동일한 필터링 조건의 페이지를 볼 수 있게 됩니다.
예시:
https://example.com/shop?category=shoes&price=50-100&sort=asc
컨텐츠의 양이 많아서 페이지 단위로 탐색해야하는 경우, 페이지네이션(Pagination)을 적용하게 됩니다.
이 때 현재 페이지 정보를 URL에 포함시키는 형태로 사용할 수 있습니다.
또한 페이지 정보뿐만 아니라, 현재 페이지의 필터링 조건도 URL에 함께 포함시켜 복합적인 상태들을 관리할 수도 있습니다.
예시:
https://example.com/articles?page=2
내가 보고 있는 웹사이트를 메신저나 SNS 등을 통해 공유하고 싶을 경우가 있습니다.
이 때 단순히 웹사이트의 주소만을 전달하게 되면, 상대방이 내가 보고 있는 페이지를 동일하게 보지 못할 수도 있습니다.
그래서 URL에 내가 보고 있는 페이지와 컨텐츠의 정보를 담아서 공유하게 되면,
공유를 받은 상대방도 완전히 동일한 페이지와 컨텐츠를 볼 수 있습니다.
예시:
https://example.com/posts/123#comments
이메일에 링크를 삽입하거나 공유된 링크를 통해 사용자의 유입 경로를 추적하고 싶을 때,
흔히 UTM이라고 알려진 파라미터들을 URL에 포함시켜서 사용합니다.
참고로 UTM은 Urchin Tracking Module의 약자로,
URL에 추가된 쿼리 파라미터를 통해 마케팅 캠페인의 유입 경로와 성과를 추적하는 표준화된 방식입니다.
예시:
https://example.com/?utm_source=newsletter&utm_medium=email
지금까지 URL State의 개념과 다양한 사용 사례에 대해 살펴보았습니다.
지금부터는 React에서 URL State를 편리하게 사용할 수 있게 해주는 nuqs에 대해 살펴보도록 하겠습니다.
nuqs는 'nukes'라고 발음하며, 공식 웹사이트에서는 nuqs를 아래와 같이 정의하고 있습니다.
"Type-safe search params state manager for React frameworks - Like useState, but stored in the URL query string."
위 문장에서 'Type-safe search params state manager'라는 부분을 주목해볼 필요가 있습니다.
한 마디로 nuqs는 React 애플리케이션에서 URL 쿼리 파라미터을 통해 상태를 관리하기 위한 타입 안전(type-safe) 라이브러리입니다.
nuqs에서는 React의 useState()
훅과 비슷한 API를 제공하며,
상태를 URL 쿼리 파라미터에 동기화하여 애플리케이션의 상태를 공유하거나 복원할 수 있도록 해줍니다.
또한 Next.js, Remix 등의 다양한 React 기반 프레임워크와 호환되며, 서버-클라이언트 간 타입 안전성을 보장합니다.
URL State 방식을 직접 적용하려고 하다보면 타입 안전성이 보장되지 않는 경우가 발생하게 되는데,
nuqs 같은 라이브러리를 사용하면 타입 안전성도 보장이 되고, 직관적인 API로 URL 상태 관리를 간소화할 수 있는 것이죠.
nuqs를 사용하기 위해서는 먼저 아래와 같이 패키지 매니저를 통해 nuqs를 설치해야 합니다.
# npm
npm install nuqs
# yarn
yarn add nuqs
# pnpm
pnpm add nuqs
이후 NuqsAdapter
라는 상위 컴포넌트(HOC, Higher Order Component)로 최상위 컴포넌트를 감싸줘야 하는데,
이때는 각 프레임워크 별 설정 방법을 참고하여 진행하면 됩니다.
아래 코드는 Next.js App Router에서 RootLayout
의 children
을 NuqsAdapter
컴포넌트로 감싸주는 예시 코드입니다.
// app/layout.tsx
import { type ReactNode } from 'react';
import { NuqsAdapter } from 'nuqs/adapters/next/app';
interface RootLayoutProps {
children: ReactNode;
}
function RootLayout({ children }: RootLayoutProps) {
return (
<html>
<body>
<NuqsAdapter>{children}</NuqsAdapter>
</body>
</html>
);
}
export default RootLayout;
이후 컴포넌트 내에서 아래와 같이 useQueryState()
훅을 사용해서 상태 값을 가져오거나 업데이트하면 됩니다.
'use client'; // NOTE: 클라이언트 컴포넌트에서만 작동함
import { useQueryState } from 'nuqs';
function MyComponent() {
const [name, setName] = useQueryState('name');
return (
<>
<h1>Hello, {name || 'Anonymous'}!</h1>
<input
value={name || ''}
onChange={(e) => setName(e.target.value)}
/>
<button onClick={() => setName(null)}>Clear</button>
</>
);
};
export default MyComponent;
이렇게 하면 URL에 ?name=이름
형태로 상태를 저장하고, 사용자가 입력한 이름 값을 URL과 동기화하게 됩니다.
그렇다면 nuqs의 주요 특징으로는 어떤 것들이 있을까요?
nuqs는 TypeScript를 기반으로 작성되었기 때문에, 쿼리 파라미터의 타입을 명시적으로 정의하고 런타임 오류를 줄입니다.
parseAsString()
, parseAsInteger()
, parseAsBoolean()
등 다양한 내장 파서를 제공하여 문자열, 숫자, 불리언 등의 타입을 안전하게 처리합니다.
또한 커스텀 파서를 정의하여 복잡한 데이터 구조도 직렬화하여 URL에 저장할 수 있습니다.
useState()
훅과 비슷한 APInuqs는 React 개발자들에게 굉장히 익숙한 useState()
훅과 비슷한 형태의, useQueryState()
훅과 useQueryStates()
훅을 제공합니다.
이 훅을 호출하면 useState()
훅과 비슷하게 상태 값과 상태 업데이트 함수를 반환합니다.
아래 예시 코드는 useQueryState()
훅을 사용하여 count
값을 관리하는 코드입니다.
const [count, setCount] = useQueryState('count', parseAsInteger.withDefault(0));
이처럼 기존에 useState()
훅을 사용하던 방식과 동일하게 사용하면 되기 때문에,
별다른 기술적 진입장벽 없이 곧바로 사용할 수 있습니다.
nuqs의 useQueryState()
훅에서 반환한 상태 업데이트 함수를 사용해서 상태를 변경할 경우,
URL이 자동으로 업데이트되고 브라우저의 뒤로 가기/앞으로 가기 버튼을 통해 상태를 복원할 수 있습니다.
이러한 히스토리 동작은 아래와 같이 옵션을 통해 제어할 수 있습니다.
// 기본값: 현재 history를 새로운 상태로 대체
useQueryState('foo', { history: 'replace' });
// 상태의 변화를 history에 추가
useQueryState('foo', { history: 'push' });
쿼리 파라미터는 기본적으로 클라이언트 컴포넌트에서만 작동합니다.
하지만 nuqs는 서버 사이드 렌더링(SSR) 환경에서도 일관된 상태 관리를 제공하기 위해,
서버 측에서 쿼리 파라미터를 파싱하기 위한 createLoader()
와 createSerializer()
함수를 제공합니다.
아래 코드는 createLoader()
함수를 사용한 예시 코드입니다.
import { createLoader } from 'nuqs'; // 또는 'nuqs/server'
const searchParams = {
q: parseAsString,
page: parseAsInteger.withDefault(1),
};
const loadSearchParams = createLoader(searchParams);
const { q, page } = loadSearchParams('?q=hello&page=2');
이번 매거진에서는 URL State 방식과 그를 위한 nuqs라는 라이브러리에 대해 살펴보았습니다.
저는 개인적으로 URL State를 굉장히 자주 사용하는데, 여러분들도 개발 중인 제품에 한 번씩 적용해보시면 좋지 않을까 싶습니다.
저는 다음에 또 유익한 글로 찾아뵙겠습니다!
지금까지 소플이었습니다. 감사합니다 😀
지금 가입하고 프론트엔드 개발 관련 매거진을 이메일로 받아보세요!