ADR-0028: Frontend Routing & Data: TanStack¶
Umbra 의 React SPA 에서 라우팅은 TanStack Router (코드 기반), 서버 상태는 TanStack Query 로 관리한다.
Status¶
Accepted
- Decided at — 2026-04-13
- Decided by — Pablo
Context¶
Vite + React SPA 를 선택한 후(ADR-0008) 라우팅 라이브러리와 데이터 페칭 전략을 정해야 한다. Dashboard 는 인증 후 사용되며 다음 특성을 가진다.
- 여러 도메인 화면 (구독, 복구, 설정, 길드 선택) 간 라우팅
- 각 route 진입 시 서버 데이터 prefetch
- Mutation 후 관련 데이터 자동 갱신
- 타입 안전성 요구 (Go 백엔드와 OpenAPI 로 타입 동기화)
라우팅 후보: React Router, TanStack Router, Wouter 데이터 페칭 후보: TanStack Query, SWR, 자체 fetch
Decision¶
두 TanStack 라이브러리를 조합으로 채택한다.
- Routing — TanStack Router, 코드 기반 모드 (파일 기반 아님)
- Server state — TanStack Query
- Client state — Zustand (별도 ADR 없음, 보조적 선택)
- Form — React Hook Form + Zod (별도 ADR 없음)
TanStack Router (code-based)¶
파일 기반이 아닌 코드 기반으로 라우트를 선언한다.
const restoreRoute = createRoute({
getParentRoute: () => dashboardRoute,
path: 'restore',
component: RestorePage,
})
TanStack Query¶
서버 데이터는 전적으로 TanStack Query 가 관리한다. Zustand 에 서버 상태를 저장하지 않는다.
const { data } = useQuery({
queryKey: ['billing', 'subscription', guildId],
queryFn: () => apiFetch(`/guilds/${guildId}/subscription`),
})
선택 근거:
- 같은 팀 개발 — TanStack Router 와 Query 가 같은 팀에서 개발되어 통합이 자연스럽다. Router loader 가 Query 를 prefetch 하는 패턴이 1급 지원됨.
- 타입 안전성 — Router 는 route params, search params 모두 타입 추론. Query 는 queryFn 반환 타입이 data 에 그대로 반영.
- 코드 기반의 장점 — 파일 위치와 라우트 구조 분리 가능. Feature-based 폴더 조직 (ADR 없음, 구조 결정사항) 과 자연 결합. 자동 생성 파일(
routeTree.gen.ts) 부담 0. - Query 는 사실상 표준 — SaaS 대시보드의 cache, invalidation, optimistic update 관리에 검증된 라이브러리.
Consequences¶
Positive¶
- Route params / search params 의 타입 안전성으로 URL 조작 버그 감소
- 서버 상태 관리가 TanStack Query 한 곳에 집중 (Zustand 에 섞이지 않음)
- Route 전환 시 데이터 prefetch 로 UX 개선
- Mutation 후 자동 invalidation 으로 stale data 방지
Negative¶
- 두 라이브러리 모두 학습 곡선 존재 (진욱 입장)
- TanStack Router 는 React Router 대비 레퍼런스가 적음
- 코드 기반 라우팅은 파일 기반 대비 보일러플레이트 약간 증가
Neutral¶
- 파일 기반 자동 생성(
routeTree.gen.ts)을 쓰지 않으므로 build step 단순 - Zustand 는 UI 상태만 담당 (토글, 모달 open 상태 등) → 책임 분리 명확
Alternatives considered¶
Alternative 1: React Router v7¶
Pros
- 생태계 최대 규모, 레퍼런스 풍부
- SPA 라우팅의 de-facto 표준
Cons
- 타입 추론이 TanStack Router 대비 약함
- loader 패턴이 Remix 와 결합되어 SPA only 사용 시 어색
- 최근 리브랜딩으로 버전별 호환성 혼란
Why rejected — TanStack Router 의 타입 안전성이 우위. React Router 의 생태계 장점이 Umbra 의 도메인 수(8~9개 route) 규모에서는 결정적이지 않음.
Alternative 2: TanStack Router (file-based)¶
Pros
- 파일 구조 = 라우트 트리 = 명시적
- React Router, Next.js 사용자에 친숙
Cons
- 자동 생성 파일(
routeTree.gen.ts) 이 commit 되거나 gitignore 되어야 함 (팀 컨벤션 필요) - Feature-based 폴더 조직과 충돌 (route 기반 강제)
Why rejected — 코드 기반이 Umbra 의 Feature-based 조직과 더 자연스럽게 결합. 자동 생성 파일 관리 부담 회피.
Alternative 3: SWR¶
Pros
- 경량, Vercel 팀 개발
- 단순한 API
Cons
- TanStack Query 대비 mutation, optimistic update 기능 덜 성숙
- TanStack Router 와 통합되지 않음
Why rejected — TanStack Query 와의 기능 격차가 있고, Router 와 같은 팀 개발이 아니라 통합 이점 없음.
Alternative 4: Wouter¶
Pros
- 매우 경량 (2KB)
Cons
- 타입 안전성 약함
- 기능 제한적
Why rejected — 결제 SaaS 대시보드의 기능 요구를 충족 못함.
Alternative 5: fetch 직접 + 자체 캐시¶
Pros
- 의존성 0
Cons
- 캐시, invalidation, retry 를 전부 자체 구현
- 보일러플레이트 폭증
Why rejected — MVP 일정에 오버엔지니어링. TanStack Query 가 이 모든 문제를 해결.
Compliance¶
apps/web/src/router.ts에 코드 기반 라우트 트리 정의- Query key 는 계층 구조 (
['billing', 'subscription', guildId]) 로 일관성 유지 - Server state 를 Zustand 에 저장 금지 (code review 에서 차단)
- 라우트 loader 는 Query prefetch 로 사용, 자체 데이터 fetch 는 피함
Revisit triggers¶
- TanStack Router 가 유지보수 정체 시 React Router 로 재평가
- 서버 상태 관리 요구가 Redux Toolkit Query 수준의 복잡도로 확장되면 재검토
- tRPC-like 엔드투엔드 타입 안전성이 필요하면 새 스택 논의