검색 파라미터(search params)는 그냥 문자열이 아니라 애플리케이션 상태다.
지금까지는:
URLSearchParams 파싱글의 주장:
전형적인 패턴:
const schema = z.object({
sort: z.enum(['asc', 'desc']),
})
const raw = Object.fromEntries(new URLSearchParams(location.href))
const result = schema.safeParse(raw)
문제점:
매번 이런 코드 반복
라우트와 스키마가 분리 → 어디가 진실인지 알 수 없음
URLSearchParams는:
파라미터가 조금만 복잡해져도 스케일이 안 맞는다.
Nuqs 같은 라이브러리들이 이 “읽기” 부분을 많이 개선했지만,
글이 보는 진짜 문제는 쓰기(write) 쪽이다.
이런 코드를 떠올려 보면:
<Link to="/dashboards/overview" search={{ sort: 'asc' }} />
실제로는:
sort가 이 라우트에서 유효한 값인지 알 수 없다.결국:
Nuqs 같은 도구:
useSearchParam 훅 제공장점:
하지만 한계:
즉, 로컬 추상화는 “편의성”은 주지만,
전역적인 조정·일관성(coordination)은 제공하지 못한다.
핵심 아이디어:
“검색 파라미터 스키마를 라우트 정의 안에 집어넣고,
라우터가 단일 소스 오브 트루스를 가진다.”
예시:
export const Route = createFileRoute('/dashboards/overview')({
validateSearch: z.object({
sort: z.enum(['asc', 'desc']),
filter: z.string().optional(),
}),
})
이렇게 하면:
/dashboards/overview에 대한 검색 파라미터 계약이 여기 한 곳에만 존재한다.<Link
to="/dashboards/overview"
search={{ sort: 'asc' }} // 타입 추론 + 검증
/>
search에 잘못된 값(예: sort: 'up')을 넘기면 타입 에러.검색 파라미터 부분 업데이트:
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는 부모에게서 자동 상속
}),
})
요청: /dashboard/123?sort=desc&filter=active
sort 검증filter 검증만약 자식에서 sort를 다른 타입으로 다시 정의하면?
validateSearch: z.object({
sort: z.boolean(), // ❌ 부모의 'asc' | 'desc'와 충돌
filter: z.string().optional(),
})
→ 타입 에러로 바로 잡힌다.
이 구조 덕분에:
page, sort 등)를정리하면, 라우터 레벨에서 검색 파라미터를 스키마와 함께 관리하면:
타입 세이프티
계약(Contract)
일관성
리액티브 모델
조정(Co-ordination)
검색 파라미터는 문자열 덩어리가 아니라 상태다.
상태라면:
그걸 앱 곳곳의 훅/유틸에 흩어놓지 말고,
라우터 안으로 끌고 들어와 단일 소스 오브 트루스로 관리하는 게 맞다.
검색 파라미터를 여전히 “문자열 파싱 대상” 정도로 취급하면:
애초에 처음부터 “상태”로 다루는 설계로 출발해야 한다는 게 이 글의 요지다.