검색 파라미터는 상태다 ("Search Params Are State" 요약)

okorion·2025년 7월 16일

원문 - Search Params Are State

2025년 6월, Tanner Linsley는 검색 파라미터를 상태(state)로 다뤄야 한다는 명확한 관점을 담은 글을 공개했습니다. 많은 React 개발자들이 URL의 query string을 단순한 문자열 조작 대상으로 취급해온 현실에서, 이 글은 검색 파라미터를 정형화된 상태로 다루어야 할 이유와, 이를 해결하기 위한 TanStack Router의 접근을 정리한 것입니다.

🧩 검색 파라미터는 상태다

전통적으로 검색 파라미터(search params)는:

  • 글로벌하다: 앱 어디서나 접근 가능
  • 직렬화 가능하다: URL에 저장되어 공유, 북마크, 복원 가능
  • 공유 가능하다: URL을 그대로 전달하면 동일한 상태로 복원됨

하지만 대부분의 앱은 이를 여전히 string parsing이나 brittle utils에 의존하고 있고, 명시적 타입 유추나 구조화된 검증이 어렵다.

const schema = z.object({ sort: z.enum(["asc", "desc"]) });
const raw = Object.fromEntries(new URLSearchParams(location.href));
const result = schema.safeParse(raw);

이런 방식은 유효성 검증이 수동적이며 중복되고, 타입 추론이나 구조적 제약이 없기 때문에 깨지기 쉽고 유지보수 비용이 높다.

❌ 검색 파라미터 작성은 더욱 치명적이다

읽는 것은 어떻게든 해결 가능하지만, URL을 작성하는 건 더 어려운 문제다.

<Link to="/dashboards/overview" search={{ sort: 'asc' }} />

이 코드에서 sort가 실제로 유효한 파라미터인지, 올바른 enum 값인지, 누가 책임지고 검증해야 하는지 알 방법이 없다. 결국:

  • 유효하지 않은 값 전달 가능
  • 스키마 중복/불일치
  • 다른 컴포넌트와의 충돌
  • 디버깅 어려움

결국, 스키마에 기반한 제약(Constraint)이 있어야 조정과 협업이 가능하다.

🧰 Nuqs 등 도구의 한계

Nuqs는 로컬 컴포넌트 레벨에서 Zod 스키마 기반의 파싱 및 작성 API를 제공하는 좋은 예시다.

하지만:

  • 스키마가 분산됨
  • 일관성이 없음
  • 라우트 간 연계가 안됨
  • 중복 선언 및 타입 드리프트 발생

검색 파라미터는 라우터 수준에서 통합 관리되어야 진정한 안정성을 확보할 수 있다.

✅ TanStack Router의 해결책

TanStack Router는 검색 파라미터를 라우트 정의에 통합함으로써 위 문제를 근본적으로 해결한다.

export const Route = createFileRoute('/dashboards/overview')({
  validateSearch: z.object({
    sort: z.enum(['asc', 'desc']),
    filter: z.string().optional(),
  }),
});
  • ✅ 검색 파라미터의 타입 검증, 유효성 검사, 기본값 설정 가능
  • ✅ 자동 추론과 자동 완성 지원
  • ✅ 불변성과 부분 업데이트 지원
navigate({
  search: (prev) => ({ ...prev, page: prev.page + 1 }),
});

🧬 계층적 검색 파라미터 검증

부모 라우트와 자식 라우트가 스키마를 안전하게 상속 및 확장할 수 있다.

// routes/dashboard.tsx
export const Route = createFileRoute('/dashboard')({
  validateSearch: z.object({
    sort: z.enum(['asc', 'desc']).default('asc'),
  }),
});

// routes/dashboard/$dashboardId.tsx
export const Route = createFileRoute('/dashboard/$dashboardId')({
  validateSearch: z.object({
    filter: z.string().optional(),
    // sort는 부모에서 자동 상속됨
  }),
});

부모가 정의한 sort 스키마를 자식이 잘못 재정의하면 타입 에러 발생:

validateSearch: z.object({
  sort: z.boolean(), // ❌ boolean은 상속된 enum에 부합하지 않음
});

🧠 검색 파라미터를 진짜 상태로 다루기 위한 조건

  • 라우트가 스키마를 소유한다 → 중앙 집중적 구조
  • 각 컴포넌트는 스키마 기반으로 파라미터를 접근한다 → 자동화된 일관성 유지
  • 컴포넌트 간 조정, 공유, 네비게이션이 안전해진다

즉, searchParams는 더 이상 문자열이 아닌 공식적인 상태의 일부로 취급되어야 한다.

🧾 결론: search params도 state다

많은 라우팅 시스템은 searchParams를 단지 보조 기능 정도로 취급한다. TanStack Router는 이를 1급 상태로 격상시켜:

  • 타입 추론 가능
  • 유효성 검증 가능
  • 읽기/쓰기 일관성 보장
  • 컴포넌트 수준 반응성 제공

URL을 더 이상 문자열로 다루지 말자. 애초에 상태(state)로 다뤘다면 생기지 않을 문제들이었다.

profile
Tech Blog

0개의 댓글