4.9 KiB
4.9 KiB
id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
| id | title | category | status | source_trust_level | verification_status | created_at | updated_at | tags | tech_stack | applied_in | aliases | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| react-tanstack-router-patterns | TanStack Router — Type-safe / loader / search params | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
TanStack Router
Type-safe React router. Search params validation, file-based routing, loader, suspense 일급. React Router 의 type-safe 버전 + Next 의 file-based.
📖 핵심 개념
- File-based:
routes/*.tsx→ 자동 tree. - Type-safe link:
<Link to="/users/$id" params={{ id: '1' }} />컴파일 검증. - Search params: zod 같은 schema.
- Loader: 라우트 진입 전 데이터 fetch.
💻 코드 패턴
Setup
yarn add @tanstack/react-router @tanstack/router-plugin
// vite.config.ts
import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
plugins: [TanStackRouterVite()];
Route 정의 (file-based)
// src/routes/__root.tsx
import { Outlet, createRootRoute } from '@tanstack/react-router';
export const Route = createRootRoute({ component: () => <Outlet /> });
// src/routes/users.$id.tsx
import { createFileRoute } from '@tanstack/react-router';
import { z } from 'zod';
export const Route = createFileRoute('/users/$id')({
params: { parse: (p) => ({ id: z.string().uuid().parse(p.id) }) },
validateSearch: z.object({ tab: z.enum(['profile', 'orders']).default('profile') }),
loader: async ({ params }) => api.user.get(params.id),
component: UserPage,
});
function UserPage() {
const { id } = Route.useParams(); // typed
const { tab } = Route.useSearch(); // typed
const user = Route.useLoaderData(); // typed
return <h1>{user.name}</h1>;
}
Link (type-safe)
import { Link } from '@tanstack/react-router';
<Link to="/users/$id" params={{ id: '42' }} search={{ tab: 'orders' }}>
Profile
</Link>
// 잘못된 path / params 컴파일 에러
Navigate (programmatic)
const navigate = Route.useNavigate();
navigate({ to: '/users/$id', params: { id: '42' } });
// 또는 search 만 update
navigate({ search: (prev) => ({ ...prev, tab: 'orders' }) });
Loader + Suspense
export const Route = createFileRoute('/posts')({
loader: async () => api.posts.list(),
pendingComponent: () => <Spinner />,
errorComponent: ({ error }) => <Error error={error} />,
component: PostsPage,
});
function PostsPage() {
const posts = Route.useLoaderData();
return ...
}
TanStack Query 통합 (loader 안)
import { queryOptions } from '@tanstack/react-query';
const userQuery = (id: string) => queryOptions({
queryKey: ['user', id],
queryFn: () => api.user.get(id),
});
export const Route = createFileRoute('/users/$id')({
loader: ({ context, params }) => context.queryClient.ensureQueryData(userQuery(params.id)),
component: UserPage,
});
function UserPage() {
const { id } = Route.useParams();
const { data } = useSuspenseQuery(userQuery(id));
return <h1>{data.name}</h1>;
}
Search params (typed link)
<Link to="/posts" search={{ q: 'react', page: 2 }}>Posts</Link>
const { q, page } = Route.useSearch();
URL: /posts?q=react&page=2.
Nested layout
// routes/_app.tsx — layout
export const Route = createFileRoute('/_app')({
component: () => (
<div className="app">
<Sidebar />
<Outlet />
</div>
),
});
// routes/_app.users.tsx — _app layout 안
export const Route = createFileRoute('/_app/users')({...});
Auth (beforeLoad)
export const Route = createFileRoute('/admin')({
beforeLoad: async ({ context }) => {
if (!context.auth.user) throw redirect({ to: '/login' });
},
});
Devtools
import { TanStackRouterDevtools } from '@tanstack/router-devtools';
<RouterProvider router={router} />
<TanStackRouterDevtools router={router} />
🤔 의사결정 기준
| 상황 | 추천 |
|---|---|
| Type-safe critical | TanStack Router |
| Next.js | Next App Router (자체) |
| 단순 SPA | React Router 6 |
| 거대 monorepo + 강 types | TanStack Router |
| File-based + SSR | Next / Remix |
| 빠른 prototype | React Router |
❌ 안티패턴
- String path concat: type 안전 X. params object.
- Search 검증 없음: 잘못된 URL 이 crash.
- Loader 안에 mutation: 의도와 다름. action 으로.
- Route 별
useParams<RouteId>()매번 type 인자: factory 활용. - beforeLoad 무거운 작업: 라우트 진입 느림.
- 404 / pendingComponent 없음: 빈 화면.
🤖 LLM 활용 힌트
- File-based + zod search/params + loader + TQ 통합 4종.
- Vite plugin 자동 generate.
- Devtools 필수 (개발).