dkmqflx's blogGithub

nuqs를 사용해서 쿼리 파라미터 관리하기

2025.02.11

nuqs란?

nuqs는 React 애플리케이션에서 URL 쿼리 파라미터를 타입 안전하게 관리할 수 있는 상태 관리 라이브러리이다. 기본적으로 useState처럼 동작하지만, 상태를 URL의 쿼리 문자열에 저장해서 페이지를 새로고침하거나 공유해도 유지할 수 있도록 해준다. 따라서 필터, 검색, 정렬, 페이지네이션 같은 기능을 구현할 때 더 직관적이고 확장성 있게 코드를 작성할 수 있다.


nuqs가 필요한 이유

React에서 URL 쿼리 파라미터를 검색어나 필터링, 페이지네이션 등의 기능에 사용한다. 이를 위해서는 아래와 같은 기능이 구현되어야 한다.

  1. 새로고침해도 상태 유지
  2. 뒤로 가기 / 앞으로 가기 가능
  3. 현재 상태를 URL로 공유 가능

하지만 useStateuseSearchParams을 사용하면 다음과 같은 한계가 있다.

useState로 상태 관리하기

useState를 사용하여 상태를 관리하게 되면 다음과 같은 단점이 있다.

  1. 새로고침 시 상태가 사라짐
  2. 뒤로 가기/앞으로 가기 기능이 동작하지 않음
  3. 현재 상태를 공유할 수 없음 (URL을 통해 상태 공유 불가)
const [search, setSearch] = useState("");

useSearchParams로 상태 관리하기

useSearchParams로 상태를 관리하게 되면 useState를 사용했을 때의 단점을 해결할 수 있지만 다음과 같은 불편함이 여전히 존재한다. 그리고 useSearchParams hook은 client component에서만 사용할 수 있다.

  1. 타입 안전성이 부족함 (searchParams.get("q")는 string | null 반환)
  2. 직렬화/파싱이 필요함 (숫자, 배열, 객체 등을 관리하기 어려움)
"use client";
 
import { useSearchParams, usePathname, useRouter } from "next/navigation";
 
export function SearchComponent() {
  const searchParams = useSearchParams();
  const pathname = usePathname();
  const router = useRouter();
 
  const search = searchParams.get("q") || "";
 
  const updateSearch = (value: string) => {
    const params = new URLSearchParams(searchParams);
    if (value) {
      params.set("q", value);
    }
 
    router.replace(`${pathname}?${params.toString()}`); // Update URL
  };
 
  return (
    <div>
      <input
        type="text"
        value={search}
        placeholder="Search..."
        onChange={(e) => updateSearch(e.target.value)}
      />
    </div>
  );
}

nuqs 사용하기

nuqs는 다음과 같은 기능을 제공해서 기존의 상태 관리 방식의 문제점을 해결한다

  1. useQueryState hook를 사용하여 string, number, boolean, object 등 다양한 타입을 안전하게 관리 가능
  2. 페이지 새로고침 후에도 상태 유지
  3. parseAsInteger, parseAsBoolean, parseAsJson 등의 parser를 지원

useQueryState

아래는 nuqs의 useQueryState를 사용한 예제이다. 사용자가 입력하면 hello 값이 setHello()를 통해 URL에 반영된다 (?hello=value). 페이지 새로고침 후에도 hello 값이 유지되고, URL을 복사해서 공유하면 같은 상태가 유지 된다.

"use client";
 
import { useQueryState } from "nuqs";
 
export function Demo() {
  const [hello, setHello] = useQueryState("hello", { defaultValue: "" });
 
  return (
    <>
      <input
        value={hello}
        placeholder="Enter your name"
        onChange={(e) => setHello(e.target.value || null)}
      />
      <p>Hello, {hello || "anonymous visitor"}!</p>
    </>
  );
}

아래는 parser를 통해서 query state를 관리하는 예제이다. count 값이 parseAsInteger를 사용해 숫자로 변환되므로, 문자열이 아닌 정수로 관리할 수 있다

import { parseAsInteger, useQueryState } from "nuqs";
 
export function Counter() {
  const [count, setCount] = useQueryState(
    "count",
    parseAsInteger.withDefault(0)
  );
 
  return (
    <>
      <button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>
    </>
  );
}

useQueryStates

useQueryStates를 사용하면 여러 개의 상태를 한 번에 관리할 수 있다. 아래 코드는 q, page, sort 쿼리 상태를 관리하는 예제이다. setQuery hook을 톨해서 쿼리를 업데이트 할 수 있고, 사용자가 뒤로가기를 누르면 이전 상태로 복원된다.

import { parseAsInteger, useQueryStates } from "nuqs";
 
export function MultiFilter() {
  const [query, setQuery] = useQueryStates({
    q: { defaultValue: "" },
    page: parseAsInteger.withDefault(1),
    sort: { defaultValue: "asc" },
  });
 
  return (
    <div>
      <input
        value={query.q}
        placeholder="Search"
        onChange={(e) => setQuery({ q: e.target.value || null })}
      />
      <button onClick={() => setQuery({ page: query.page + 1 })}>
        Next Page
      </button>
 
      <button
        onClick={() =>
          setQuery({ sort: query.sort === "asc" ? "desc" : "asc" })
        }
      >
        Toggle Sort ({query.sort})
      </button>
    </div>
  );
}

서버 컴포넌트에서 nuqs 사용하기

nuqs는 서버 컴포넌트에서도 사용할 수 있다. 아래 코드와 같이 searchParams props를 통해 q 값을 가져와서 API 요청을 보내고 결과를 렌더링할 수 있다.

import { parseAsString } from "nuqs";
import { cookies } from "next/headers";
 
export async function SearchResults({
  searchParams,
}: {
  searchParams: { q?: string };
}) {
  const q = parseAsString.withDefault("")(searchParams.q);
 
  const data = q
    ? await fetch(`https://api.example.com/search?q=${q}`).then((res) =>
        res.json()
      )
    : [];
 
  return (
    <div>
      <h1>Search Results for: {q}</h1>
      <ul>
        {data.map((item: { id: string; title: string }) => (
          <li key={item.id}>{item.title}</li>
        ))}
      </ul>
    </div>
  );
}

결론

nuqs는 기존 useState나 useSearchParams의 단점을 보완하여 타입 안전하고 직관적인 방식으로 URL 상태를 관리할 수 있는 강력한 도구이다. nuqs에서 제공하는 hook을 통해 검색, 필터링, 페이지네이션과 같은 기능들을 더 수월하게 구현할 수 있다. 그리고 Next.js의 클라이언트 컴포넌트와 서버 컴포넌트 모두에서 활용할 수 있다는 장점도 있다.