@kood/claude-code 0.2.5 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +13 -8
- package/package.json +1 -1
- package/templates/.claude/agents/code-reviewer.md +371 -19
- package/templates/.claude/agents/dependency-manager.md +197 -0
- package/templates/.claude/agents/deployment-validator.md +136 -0
- package/templates/.claude/agents/git-operator.md +147 -0
- package/templates/.claude/agents/implementation-executor.md +202 -0
- package/templates/.claude/agents/lint-fixer.md +155 -0
- package/templates/.claude/agents/refactor-advisor.md +339 -29
- package/templates/.claude/commands/agent-creator.md +355 -0
- package/templates/.claude/commands/docs-creator.md +404 -163
- package/templates/.claude/commands/docs-refactor.md +400 -113
- package/templates/.claude/commands/execute.md +357 -185
- package/templates/.claude/commands/git-all.md +65 -71
- package/templates/.claude/commands/git-session.md +80 -64
- package/templates/.claude/commands/git.md +68 -72
- package/templates/.claude/commands/lint-fix.md +224 -109
- package/templates/.claude/commands/lint-init.md +142 -168
- package/templates/.claude/commands/plan.md +300 -84
- package/templates/.claude/commands/prd.md +497 -214
- package/templates/.claude/commands/pre-deploy.md +242 -0
- package/templates/.claude/commands/subagent-creator.md +118 -0
- package/templates/.claude/commands/version-update.md +45 -57
- package/templates/hono/CLAUDE.md +99 -54
- package/templates/hono/docs/guides/conventions.md +352 -0
- package/templates/hono/docs/guides/env-setup.md +347 -0
- package/templates/hono/docs/guides/getting-started.md +239 -0
- package/templates/hono/docs/library/hono/error-handling.md +20 -29
- package/templates/hono/docs/library/hono/index.md +25 -52
- package/templates/hono/docs/library/hono/middleware.md +16 -75
- package/templates/hono/docs/library/hono/rpc.md +7 -35
- package/templates/hono/docs/library/hono/validation.md +25 -45
- package/templates/hono/docs/library/t3-env/index.md +374 -0
- package/templates/npx/CLAUDE.md +165 -65
- package/templates/npx/docs/library/commander/index.md +10 -73
- package/templates/npx/docs/library/fs-extra/index.md +21 -113
- package/templates/npx/docs/library/prompts/index.md +30 -176
- package/templates/npx/docs/references/patterns.md +75 -48
- package/templates/tanstack-start/CLAUDE.md +101 -77
- package/templates/tanstack-start/docs/architecture.md +427 -0
- package/templates/tanstack-start/docs/design.md +558 -0
- package/templates/tanstack-start/docs/guides/conventions.md +132 -32
- package/templates/tanstack-start/docs/guides/env-setup.md +127 -62
- package/templates/tanstack-start/docs/guides/getting-started.md +81 -20
- package/templates/tanstack-start/docs/guides/hooks.md +241 -36
- package/templates/tanstack-start/docs/guides/routes.md +213 -61
- package/templates/tanstack-start/docs/guides/services.md +260 -24
- package/templates/tanstack-start/docs/library/better-auth/index.md +469 -16
- package/templates/tanstack-start/docs/library/t3-env/index.md +307 -0
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +12 -21
- package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +22 -35
- package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +7 -24
- package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +26 -39
- package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +23 -26
- package/templates/tanstack-start/docs/library/tanstack-router/error-handling.md +32 -147
- package/templates/tanstack-start/docs/library/tanstack-router/hooks.md +25 -167
- package/templates/tanstack-start/docs/library/tanstack-router/index.md +39 -74
- package/templates/tanstack-start/docs/library/tanstack-router/navigation.md +46 -116
- package/templates/tanstack-start/docs/library/tanstack-router/route-context.md +35 -154
- package/templates/tanstack-start/docs/library/tanstack-router/search-params.md +32 -171
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +7 -15
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +16 -23
- package/templates/tanstack-start/docs/library/zod/complex-types.md +12 -31
- package/templates/tanstack-start/docs/library/zod/index.md +18 -35
- package/templates/tanstack-start/docs/library/zod/transforms.md +11 -25
- package/templates/tanstack-start/docs/library/zod/validation.md +12 -34
- package/templates/.claude/agents/debug-detective.md +0 -37
- package/templates/.claude/agents/test-writer.md +0 -41
- package/templates/.claude/commands/feedback.md +0 -199
- package/templates/.claude/commands/ts-fix.md +0 -176
- package/templates/.claude/skills/command-creator/LICENSE.txt +0 -202
- package/templates/.claude/skills/command-creator/SKILL.md +0 -245
- package/templates/.claude/skills/command-creator/scripts/init_command.py +0 -244
- package/templates/.claude/skills/command-creator/scripts/package_command.py +0 -125
- package/templates/.claude/skills/command-creator/scripts/quick_validate.py +0 -143
- package/templates/.claude/skills/frontend-design/SKILL.md +0 -310
- package/templates/.claude/skills/frontend-design/references/animation-patterns.md +0 -446
- package/templates/.claude/skills/frontend-design/references/colors-2026.md +0 -244
- package/templates/.claude/skills/frontend-design/references/typography-2026.md +0 -302
- package/templates/.claude/skills/gemini-review/SKILL.md +0 -118
- package/templates/.claude/skills/gemini-review/references/checklists.md +0 -129
- package/templates/.claude/skills/gemini-review/references/prompt-templates.md +0 -274
- package/templates/.claude/skills/skill-creator/LICENSE.txt +0 -202
- package/templates/.claude/skills/skill-creator/SKILL.md +0 -184
- package/templates/.claude/skills/skill-creator/scripts/init_skill.py +0 -303
- package/templates/.claude/skills/skill-creator/scripts/package_skill.py +0 -110
- package/templates/.claude/skills/skill-creator/scripts/quick_validate.py +0 -65
- package/templates/hono/docs/library/ai-sdk/index.md +0 -190
- package/templates/hono/docs/library/ai-sdk/openrouter.md +0 -111
- package/templates/hono/docs/library/ai-sdk/providers.md +0 -102
- package/templates/hono/docs/library/ai-sdk/streaming.md +0 -146
- package/templates/hono/docs/library/ai-sdk/structured-output.md +0 -161
- package/templates/hono/docs/library/ai-sdk/tools.md +0 -144
- package/templates/hono/docs/library/drizzle/cloudflare-d1.md +0 -247
- package/templates/hono/docs/library/drizzle/config.md +0 -167
- package/templates/hono/docs/library/drizzle/index.md +0 -259
- package/templates/hono/docs/library/hono/env-setup.md +0 -169
- package/templates/hono/docs/library/pino/index.md +0 -146
- package/templates/tanstack-start/docs/architecture/architecture.md +0 -243
- package/templates/tanstack-start/docs/deployment/cloudflare.md +0 -132
- package/templates/tanstack-start/docs/deployment/index.md +0 -163
- package/templates/tanstack-start/docs/deployment/nitro.md +0 -110
- package/templates/tanstack-start/docs/deployment/railway.md +0 -147
- package/templates/tanstack-start/docs/deployment/vercel.md +0 -135
- package/templates/tanstack-start/docs/design/components.md +0 -175
- package/templates/tanstack-start/docs/design/index.md +0 -151
- package/templates/tanstack-start/docs/design/safe-area.md +0 -118
- package/templates/tanstack-start/docs/design/tailwind-setup.md +0 -156
- package/templates/tanstack-start/docs/library/ai-sdk/hooks.md +0 -472
- package/templates/tanstack-start/docs/library/ai-sdk/index.md +0 -264
- package/templates/tanstack-start/docs/library/ai-sdk/openrouter.md +0 -371
- package/templates/tanstack-start/docs/library/ai-sdk/providers.md +0 -403
- package/templates/tanstack-start/docs/library/ai-sdk/streaming.md +0 -320
- package/templates/tanstack-start/docs/library/ai-sdk/structured-output.md +0 -454
- package/templates/tanstack-start/docs/library/ai-sdk/tools.md +0 -473
- package/templates/tanstack-start/docs/library/better-auth/2fa.md +0 -48
- package/templates/tanstack-start/docs/library/better-auth/advanced.md +0 -55
- package/templates/tanstack-start/docs/library/better-auth/plugins.md +0 -34
- package/templates/tanstack-start/docs/library/better-auth/session.md +0 -47
- package/templates/tanstack-start/docs/library/better-auth/setup.md +0 -41
- package/templates/tanstack-start/docs/library/drizzle/cloudflare-d1.md +0 -147
- package/templates/tanstack-start/docs/library/drizzle/config.md +0 -118
- package/templates/tanstack-start/docs/library/drizzle/crud.md +0 -205
- package/templates/tanstack-start/docs/library/drizzle/index.md +0 -79
- package/templates/tanstack-start/docs/library/drizzle/relations.md +0 -202
- package/templates/tanstack-start/docs/library/drizzle/schema.md +0 -154
- package/templates/tanstack-start/docs/library/drizzle/setup.md +0 -96
- package/templates/tanstack-start/docs/library/drizzle/transactions.md +0 -127
- package/templates/tanstack-start/docs/library/pino/index.md +0 -320
- /package/templates/hono/docs/{architecture/architecture.md → architecture.md} +0 -0
|
@@ -1,29 +1,13 @@
|
|
|
1
1
|
# TanStack Router - Navigation
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Link 컴포넌트
|
|
3
|
+
<patterns>
|
|
6
4
|
|
|
7
5
|
```tsx
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// 기본
|
|
6
|
+
// Link
|
|
11
7
|
<Link to="/about">About</Link>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<Link to="/
|
|
15
|
-
Post 123
|
|
16
|
-
</Link>
|
|
17
|
-
|
|
18
|
-
// Search params
|
|
19
|
-
<Link to="/products" search={{ page: 1, sort: 'newest' }}>
|
|
20
|
-
Products
|
|
21
|
-
</Link>
|
|
22
|
-
|
|
23
|
-
// Search params 병합
|
|
24
|
-
<Link to="/products" search={prev => ({ ...prev, page: 2 })}>
|
|
25
|
-
Next Page
|
|
26
|
-
</Link>
|
|
8
|
+
<Link to="/posts/$postId" params={{ postId: '123' }}>Post 123</Link>
|
|
9
|
+
<Link to="/products" search={{ page: 1, sort: 'newest' }}>Products</Link>
|
|
10
|
+
<Link to="/products" search={prev => ({ ...prev, page: 2 })}>Next</Link>
|
|
27
11
|
|
|
28
12
|
// Active 스타일
|
|
29
13
|
<Link
|
|
@@ -33,72 +17,59 @@ import { Link } from '@tanstack/react-router'
|
|
|
33
17
|
>
|
|
34
18
|
About
|
|
35
19
|
</Link>
|
|
20
|
+
<Link to="/" activeOptions={{ exact: true }}>Home</Link>
|
|
36
21
|
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
Home
|
|
40
|
-
</Link>
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Link Props
|
|
44
|
-
|
|
45
|
-
| Prop | 타입 | 설명 |
|
|
46
|
-
|------|------|------|
|
|
47
|
-
| `to` | string | 목적지 경로 |
|
|
48
|
-
| `params` | object | Path 파라미터 |
|
|
49
|
-
| `search` | object \| function | Search params |
|
|
50
|
-
| `hash` | string | Hash |
|
|
51
|
-
| `replace` | boolean | history replace |
|
|
52
|
-
| `preload` | 'intent' \| 'render' \| 'viewport' | Preload 전략 |
|
|
53
|
-
| `activeProps` | object | Active 시 props |
|
|
54
|
-
| `inactiveProps` | object | Inactive 시 props |
|
|
55
|
-
|
|
56
|
-
## useNavigate
|
|
57
|
-
|
|
58
|
-
```tsx
|
|
59
|
-
import { useNavigate } from '@tanstack/react-router'
|
|
60
|
-
|
|
61
|
-
function Component() {
|
|
22
|
+
// useNavigate
|
|
23
|
+
const Component = () => {
|
|
62
24
|
const navigate = useNavigate()
|
|
63
25
|
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
}
|
|
26
|
+
const goToAbout = () => navigate({ to: '/about' })
|
|
27
|
+
const goToPost = (id: string) => navigate({ to: '/posts/$postId', params: { postId: id } })
|
|
28
|
+
const updateSearch = () => navigate({ to: '/products', search: prev => ({ ...prev, page: 2 }) })
|
|
29
|
+
const replaceRoute = () => navigate({ to: '/login', replace: true })
|
|
30
|
+
const goUp = () => navigate({ to: '..' })
|
|
68
31
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
navigate({ to: '/posts/$postId', params: { postId } })
|
|
72
|
-
}
|
|
32
|
+
return <button onClick={goToAbout}>Go</button>
|
|
33
|
+
}
|
|
73
34
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
search: prev => ({ ...prev, page: 2 }),
|
|
79
|
-
})
|
|
80
|
-
}
|
|
35
|
+
// Preloading
|
|
36
|
+
<Link to="/posts" preload="intent">Posts</Link> // hover 시
|
|
37
|
+
<Link to="/dashboard" preload="render">Dash</Link> // 렌더링 시
|
|
38
|
+
<Link to="/products" preload="viewport">Prod</Link> // viewport 진입 시
|
|
81
39
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
40
|
+
// 조건부 네비게이션
|
|
41
|
+
const SubmitButton = () => {
|
|
42
|
+
const navigate = useNavigate()
|
|
43
|
+
const [isPending, startTransition] = useTransition()
|
|
86
44
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
45
|
+
const handleSubmit = async () => {
|
|
46
|
+
const result = await submitForm()
|
|
47
|
+
if (result.success) {
|
|
48
|
+
startTransition(() => navigate({ to: '/success' }))
|
|
49
|
+
}
|
|
90
50
|
}
|
|
91
51
|
|
|
92
|
-
return
|
|
93
|
-
<button onClick={goToAbout}>Go to About</button>
|
|
94
|
-
)
|
|
52
|
+
return <button onClick={handleSubmit} disabled={isPending}>Submit</button>
|
|
95
53
|
}
|
|
96
54
|
```
|
|
97
55
|
|
|
98
|
-
|
|
56
|
+
</patterns>
|
|
57
|
+
|
|
58
|
+
<options>
|
|
99
59
|
|
|
100
|
-
|
|
|
101
|
-
|
|
60
|
+
| Link Props | 타입 | 설명 |
|
|
61
|
+
|------------|------|------|
|
|
62
|
+
| `to` | string | 목적지 경로 |
|
|
63
|
+
| `params` | object | Path 파라미터 |
|
|
64
|
+
| `search` | object \| function | Search params |
|
|
65
|
+
| `hash` | string | Hash |
|
|
66
|
+
| `replace` | boolean | history replace |
|
|
67
|
+
| `preload` | 'intent' \| 'render' \| 'viewport' | Preload 전략 |
|
|
68
|
+
| `activeProps` | object | Active 시 props |
|
|
69
|
+
| `inactiveProps` | object | Inactive 시 props |
|
|
70
|
+
|
|
71
|
+
| navigate 옵션 | 타입 | 설명 |
|
|
72
|
+
|---------------|------|------|
|
|
102
73
|
| `to` | string | 목적지 경로 |
|
|
103
74
|
| `params` | object | Path 파라미터 |
|
|
104
75
|
| `search` | object \| function | Search params |
|
|
@@ -106,45 +77,4 @@ function Component() {
|
|
|
106
77
|
| `replace` | boolean | history.replace 사용 |
|
|
107
78
|
| `resetScroll` | boolean | 스크롤 리셋 |
|
|
108
79
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
```tsx
|
|
112
|
-
// hover 시 preload
|
|
113
|
-
<Link to="/posts" preload="intent">
|
|
114
|
-
Posts
|
|
115
|
-
</Link>
|
|
116
|
-
|
|
117
|
-
// 렌더링 시 preload
|
|
118
|
-
<Link to="/dashboard" preload="render">
|
|
119
|
-
Dashboard
|
|
120
|
-
</Link>
|
|
121
|
-
|
|
122
|
-
// viewport 진입 시 preload
|
|
123
|
-
<Link to="/products" preload="viewport">
|
|
124
|
-
Products
|
|
125
|
-
</Link>
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## 조건부 네비게이션
|
|
129
|
-
|
|
130
|
-
```tsx
|
|
131
|
-
function SubmitButton() {
|
|
132
|
-
const navigate = useNavigate()
|
|
133
|
-
const [isPending, startTransition] = useTransition()
|
|
134
|
-
|
|
135
|
-
const handleSubmit = async () => {
|
|
136
|
-
const result = await submitForm()
|
|
137
|
-
if (result.success) {
|
|
138
|
-
startTransition(() => {
|
|
139
|
-
navigate({ to: '/success' })
|
|
140
|
-
})
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return (
|
|
145
|
-
<button onClick={handleSubmit} disabled={isPending}>
|
|
146
|
-
Submit
|
|
147
|
-
</button>
|
|
148
|
-
)
|
|
149
|
-
}
|
|
150
|
-
```
|
|
80
|
+
</options>
|
|
@@ -1,181 +1,79 @@
|
|
|
1
1
|
# TanStack Router - Route Context
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## beforeLoad
|
|
6
|
-
|
|
7
|
-
라우트 로드 전 실행. 인증 체크, 리다이렉트, context 추가.
|
|
3
|
+
<patterns>
|
|
8
4
|
|
|
9
5
|
```tsx
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
// beforeLoad: 인증 체크 + context 추가
|
|
12
7
|
export const Route = createFileRoute('/dashboard')({
|
|
13
8
|
beforeLoad: async ({ context, location }) => {
|
|
14
|
-
// context에서 인증 상태 확인
|
|
15
9
|
if (!context.auth.isAuthenticated) {
|
|
16
|
-
throw redirect({
|
|
17
|
-
to: '/login',
|
|
18
|
-
search: { redirect: location.href },
|
|
19
|
-
})
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// 추가 context 반환 (loader에서 사용 가능)
|
|
23
|
-
return {
|
|
24
|
-
userPermissions: await fetchPermissions(context.auth.user.id),
|
|
10
|
+
throw redirect({ to: '/login', search: { redirect: location.href } })
|
|
25
11
|
}
|
|
12
|
+
return { userPermissions: await fetchPermissions(context.auth.user.id) }
|
|
26
13
|
},
|
|
27
|
-
loader: async ({ context }) =>
|
|
28
|
-
// beforeLoad에서 반환한 context 사용
|
|
29
|
-
return fetchDashboardData(context.userPermissions)
|
|
30
|
-
},
|
|
14
|
+
loader: async ({ context }) => fetchDashboardData(context.userPermissions),
|
|
31
15
|
component: DashboardPage,
|
|
32
16
|
})
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Protected Routes (Layout)
|
|
36
|
-
|
|
37
|
-
pathless layout으로 보호된 라우트 그룹 생성.
|
|
38
|
-
|
|
39
|
-
### _authed.tsx (Layout)
|
|
40
|
-
|
|
41
|
-
```tsx
|
|
42
|
-
// routes/_authed.tsx
|
|
43
|
-
import { createFileRoute, redirect, Outlet } from '@tanstack/react-router'
|
|
44
|
-
import { getCurrentUser } from '@/functions/auth'
|
|
45
17
|
|
|
18
|
+
// Protected Routes: _authed.tsx (pathless layout)
|
|
46
19
|
export const Route = createFileRoute('/_authed')({
|
|
47
20
|
beforeLoad: async ({ location }) => {
|
|
48
21
|
const user = await getCurrentUser()
|
|
49
|
-
|
|
50
|
-
if (!user) {
|
|
51
|
-
throw redirect({
|
|
52
|
-
to: '/login',
|
|
53
|
-
search: { redirect: location.href },
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// 하위 라우트에서 사용할 context
|
|
22
|
+
if (!user) throw redirect({ to: '/login', search: { redirect: location.href } })
|
|
58
23
|
return { user }
|
|
59
24
|
},
|
|
60
25
|
component: () => <Outlet />,
|
|
61
26
|
})
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### _authed/dashboard.tsx
|
|
65
|
-
|
|
66
|
-
```tsx
|
|
67
|
-
// routes/_authed/dashboard.tsx
|
|
68
|
-
import { createFileRoute } from '@tanstack/react-router'
|
|
69
27
|
|
|
28
|
+
// _authed/dashboard.tsx
|
|
70
29
|
export const Route = createFileRoute('/_authed/dashboard')({
|
|
71
30
|
component: DashboardPage,
|
|
72
31
|
})
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const { user } = Route.useRouteContext()
|
|
77
|
-
|
|
78
|
-
return (
|
|
79
|
-
<div>
|
|
80
|
-
<h1>Welcome, {user.name}!</h1>
|
|
81
|
-
</div>
|
|
82
|
-
)
|
|
32
|
+
const DashboardPage = () => {
|
|
33
|
+
const { user } = Route.useRouteContext() // _authed에서 전달된 context
|
|
34
|
+
return <h1>Welcome, {user.name}!</h1>
|
|
83
35
|
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### 구조
|
|
87
|
-
|
|
88
|
-
```
|
|
89
|
-
routes/
|
|
90
|
-
├── _authed.tsx # Protected layout (beforeLoad에서 인증 체크)
|
|
91
|
-
├── _authed/
|
|
92
|
-
│ ├── dashboard.tsx # /dashboard (protected)
|
|
93
|
-
│ ├── settings.tsx # /settings (protected)
|
|
94
|
-
│ └── profile.tsx # /profile (protected)
|
|
95
|
-
├── login.tsx # /login (public)
|
|
96
|
-
└── index.tsx # / (public)
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Root Context
|
|
100
|
-
|
|
101
|
-
전역 context 설정 (QueryClient, Auth 등).
|
|
102
|
-
|
|
103
|
-
### __root.tsx
|
|
104
|
-
|
|
105
|
-
```tsx
|
|
106
|
-
// routes/__root.tsx
|
|
107
|
-
import { createRootRouteWithContext, Outlet } from '@tanstack/react-router'
|
|
108
|
-
import type { QueryClient } from '@tanstack/react-query'
|
|
109
36
|
|
|
37
|
+
// Root Context
|
|
110
38
|
interface RouterContext {
|
|
111
39
|
queryClient: QueryClient
|
|
112
|
-
auth: {
|
|
113
|
-
isAuthenticated: boolean
|
|
114
|
-
user: { id: string; name: string } | null
|
|
115
|
-
}
|
|
40
|
+
auth: { isAuthenticated: boolean; user: User | null }
|
|
116
41
|
}
|
|
117
42
|
|
|
118
43
|
export const Route = createRootRouteWithContext<RouterContext>()({
|
|
119
44
|
component: RootLayout,
|
|
120
45
|
})
|
|
121
46
|
|
|
122
|
-
|
|
123
|
-
return (
|
|
124
|
-
<div>
|
|
125
|
-
<Outlet />
|
|
126
|
-
</div>
|
|
127
|
-
)
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Router 생성
|
|
132
|
-
|
|
133
|
-
```tsx
|
|
134
|
-
// app/router.tsx
|
|
135
|
-
import { createRouter } from '@tanstack/react-router'
|
|
136
|
-
import { routeTree } from './routeTree.gen'
|
|
137
|
-
import { queryClient } from './query-client'
|
|
138
|
-
|
|
139
|
-
export const router = createRouter({
|
|
47
|
+
const router = createRouter({
|
|
140
48
|
routeTree,
|
|
141
|
-
context: {
|
|
142
|
-
queryClient,
|
|
143
|
-
auth: {
|
|
144
|
-
isAuthenticated: false,
|
|
145
|
-
user: null,
|
|
146
|
-
},
|
|
147
|
-
},
|
|
49
|
+
context: { queryClient, auth: { isAuthenticated: false, user: null } },
|
|
148
50
|
})
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
## redirect
|
|
152
|
-
|
|
153
|
-
```tsx
|
|
154
|
-
import { redirect } from '@tanstack/react-router'
|
|
155
51
|
|
|
156
|
-
//
|
|
52
|
+
// redirect
|
|
157
53
|
throw redirect({ to: '/login' })
|
|
54
|
+
throw redirect({ to: '/login', search: { redirect: '/dashboard' } })
|
|
55
|
+
throw redirect({ to: '/posts/$postId', params: { postId: '123' } })
|
|
56
|
+
throw redirect({ to: '/home', replace: true })
|
|
57
|
+
```
|
|
158
58
|
|
|
159
|
-
|
|
160
|
-
throw redirect({
|
|
161
|
-
to: '/login',
|
|
162
|
-
search: { redirect: '/dashboard' },
|
|
163
|
-
})
|
|
59
|
+
</patterns>
|
|
164
60
|
|
|
165
|
-
|
|
166
|
-
throw redirect({
|
|
167
|
-
to: '/posts/$postId',
|
|
168
|
-
params: { postId: '123' },
|
|
169
|
-
})
|
|
61
|
+
<structure>
|
|
170
62
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
63
|
+
```
|
|
64
|
+
routes/
|
|
65
|
+
├── _authed.tsx # Protected layout
|
|
66
|
+
├── _authed/
|
|
67
|
+
│ ├── dashboard.tsx # /dashboard (protected)
|
|
68
|
+
│ ├── settings.tsx # /settings (protected)
|
|
69
|
+
│ └── profile.tsx # /profile (protected)
|
|
70
|
+
├── login.tsx # /login (public)
|
|
71
|
+
└── index.tsx # / (public)
|
|
176
72
|
```
|
|
177
73
|
|
|
178
|
-
|
|
74
|
+
</structure>
|
|
75
|
+
|
|
76
|
+
<usage>
|
|
179
77
|
|
|
180
78
|
| 위치 | 접근 방법 |
|
|
181
79
|
|------|----------|
|
|
@@ -183,21 +81,4 @@ throw redirect({
|
|
|
183
81
|
| loader | `{ context }` 파라미터 |
|
|
184
82
|
| component | `Route.useRouteContext()` |
|
|
185
83
|
|
|
186
|
-
|
|
187
|
-
export const Route = createFileRoute('/example')({
|
|
188
|
-
beforeLoad: ({ context }) => {
|
|
189
|
-
console.log(context.auth)
|
|
190
|
-
return { extra: 'data' }
|
|
191
|
-
},
|
|
192
|
-
loader: ({ context }) => {
|
|
193
|
-
// beforeLoad 반환값도 포함됨
|
|
194
|
-
console.log(context.extra)
|
|
195
|
-
},
|
|
196
|
-
component: ExamplePage,
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
function ExamplePage() {
|
|
200
|
-
const context = Route.useRouteContext()
|
|
201
|
-
// context.auth, context.extra 모두 접근 가능
|
|
202
|
-
}
|
|
203
|
-
```
|
|
84
|
+
</usage>
|
|
@@ -1,213 +1,74 @@
|
|
|
1
1
|
# TanStack Router - Search Params
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## 기본 사용
|
|
3
|
+
<patterns>
|
|
6
4
|
|
|
7
5
|
```tsx
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
// Zod 스키마 + 라우트
|
|
7
|
+
const searchSchema = z.object({
|
|
8
|
+
page: z.number().catch(1), // 기본값
|
|
9
|
+
search: z.string().optional(), // 선택
|
|
10
|
+
sort: z.enum(['newest', 'price']).catch('newest'),
|
|
11
|
+
tags: z.array(z.string()).catch([]), // 배열
|
|
12
|
+
inStock: z.boolean().catch(true), // Boolean
|
|
13
|
+
from: z.string().date().optional(), // 날짜
|
|
14
|
+
minPrice: z.number().min(0).catch(0), // 범위
|
|
16
15
|
})
|
|
17
16
|
|
|
18
|
-
type ProductSearch = z.infer<typeof productSearchSchema>
|
|
19
|
-
|
|
20
|
-
// 라우트에 적용
|
|
21
17
|
export const Route = createFileRoute('/products')({
|
|
22
|
-
validateSearch:
|
|
18
|
+
validateSearch: searchSchema,
|
|
19
|
+
loaderDeps: ({ search }) => ({ search }), // search 변경 시 loader 재실행
|
|
20
|
+
loader: async ({ deps: { search } }) => fetchProducts(search),
|
|
23
21
|
component: ProductsPage,
|
|
24
22
|
})
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<div>
|
|
32
|
-
<p>Page: {page}</p>
|
|
33
|
-
<p>Filter: {filter}</p>
|
|
34
|
-
<p>Sort: {sort}</p>
|
|
35
|
-
</div>
|
|
36
|
-
)
|
|
24
|
+
const ProductsPage = () => {
|
|
25
|
+
const { page, search, sort } = Route.useSearch()
|
|
26
|
+
return <div>Page: {page}, Sort: {sort}</div>
|
|
37
27
|
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Zod 스키마 패턴
|
|
41
|
-
|
|
42
|
-
```tsx
|
|
43
|
-
import { z } from 'zod'
|
|
44
|
-
|
|
45
|
-
// 기본값 (.catch)
|
|
46
|
-
const schema = z.object({
|
|
47
|
-
page: z.number().catch(1), // 파싱 실패 시 1
|
|
48
|
-
search: z.string().optional(), // undefined 허용
|
|
49
|
-
tab: z.enum(['all', 'active']).catch('all'),
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
// 복잡한 타입
|
|
53
|
-
const advancedSchema = z.object({
|
|
54
|
-
// 배열
|
|
55
|
-
tags: z.array(z.string()).catch([]),
|
|
56
|
-
|
|
57
|
-
// 날짜
|
|
58
|
-
from: z.string().date().optional(),
|
|
59
|
-
to: z.string().date().optional(),
|
|
60
|
-
|
|
61
|
-
// 숫자 범위
|
|
62
|
-
minPrice: z.number().min(0).catch(0),
|
|
63
|
-
maxPrice: z.number().max(10000).catch(10000),
|
|
64
|
-
|
|
65
|
-
// Boolean
|
|
66
|
-
inStock: z.boolean().catch(true),
|
|
67
|
-
})
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Search Params 업데이트
|
|
71
|
-
|
|
72
|
-
### Link로 업데이트
|
|
73
|
-
|
|
74
|
-
```tsx
|
|
75
|
-
import { Link } from '@tanstack/react-router'
|
|
76
|
-
|
|
77
|
-
// 전체 교체
|
|
78
|
-
<Link to="/products" search={{ page: 1, sort: 'newest' }}>
|
|
79
|
-
Reset
|
|
80
|
-
</Link>
|
|
81
28
|
|
|
82
|
-
//
|
|
83
|
-
<Link to="/products" search={
|
|
84
|
-
|
|
85
|
-
</Link>
|
|
29
|
+
// Link로 업데이트
|
|
30
|
+
<Link to="/products" search={{ page: 1, sort: 'newest' }}>Reset</Link>
|
|
31
|
+
<Link to="/products" search={prev => ({ ...prev, page: 2 })}>Next</Link>
|
|
86
32
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
to="/products"
|
|
90
|
-
search={prev => ({ ...prev, sort: 'price' })}
|
|
91
|
-
>
|
|
92
|
-
Sort by Price
|
|
93
|
-
</Link>
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### useNavigate로 업데이트
|
|
97
|
-
|
|
98
|
-
```tsx
|
|
99
|
-
import { useNavigate } from '@tanstack/react-router'
|
|
100
|
-
|
|
101
|
-
function Pagination() {
|
|
33
|
+
// useNavigate로 업데이트
|
|
34
|
+
const Pagination = () => {
|
|
102
35
|
const navigate = useNavigate()
|
|
103
36
|
const { page } = Route.useSearch()
|
|
104
37
|
|
|
105
38
|
const goToPage = (newPage: number) => {
|
|
106
|
-
navigate({
|
|
107
|
-
to: '/products',
|
|
108
|
-
search: prev => ({ ...prev, page: newPage }),
|
|
109
|
-
})
|
|
39
|
+
navigate({ to: '/products', search: prev => ({ ...prev, page: newPage }) })
|
|
110
40
|
}
|
|
111
41
|
|
|
112
42
|
return (
|
|
113
43
|
<div>
|
|
114
|
-
<button onClick={() => goToPage(page - 1)} disabled={page <= 1}>
|
|
115
|
-
Prev
|
|
116
|
-
</button>
|
|
44
|
+
<button onClick={() => goToPage(page - 1)} disabled={page <= 1}>Prev</button>
|
|
117
45
|
<span>Page {page}</span>
|
|
118
|
-
<button onClick={() => goToPage(page + 1)}>
|
|
119
|
-
Next
|
|
120
|
-
</button>
|
|
46
|
+
<button onClick={() => goToPage(page + 1)}>Next</button>
|
|
121
47
|
</div>
|
|
122
48
|
)
|
|
123
49
|
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
## 실전 예시
|
|
127
|
-
|
|
128
|
-
### 필터 + 정렬 + 페이지네이션
|
|
129
|
-
|
|
130
|
-
```tsx
|
|
131
|
-
import { createFileRoute, Link, useNavigate } from '@tanstack/react-router'
|
|
132
|
-
import { z } from 'zod'
|
|
133
|
-
|
|
134
|
-
const searchSchema = z.object({
|
|
135
|
-
page: z.number().min(1).catch(1),
|
|
136
|
-
pageSize: z.number().catch(10),
|
|
137
|
-
search: z.string().catch(''),
|
|
138
|
-
category: z.enum(['all', 'tech', 'lifestyle']).catch('all'),
|
|
139
|
-
sort: z.enum(['newest', 'oldest', 'popular']).catch('newest'),
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
export const Route = createFileRoute('/posts')({
|
|
143
|
-
validateSearch: searchSchema,
|
|
144
|
-
loaderDeps: ({ search }) => ({ search }), // search 변경 시 loader 재실행
|
|
145
|
-
loader: async ({ deps: { search } }) => {
|
|
146
|
-
return fetchPosts(search)
|
|
147
|
-
},
|
|
148
|
-
component: PostsPage,
|
|
149
|
-
})
|
|
150
50
|
|
|
151
|
-
|
|
51
|
+
// 실전: 필터 + 정렬 + 페이지네이션
|
|
52
|
+
const PostsPage = () => {
|
|
152
53
|
const { page, search, category, sort } = Route.useSearch()
|
|
153
54
|
const posts = Route.useLoaderData()
|
|
154
55
|
const navigate = useNavigate()
|
|
155
56
|
|
|
156
|
-
const updateSearch = (updates: Partial<typeof searchSchema
|
|
157
|
-
navigate({
|
|
158
|
-
to: '/posts',
|
|
159
|
-
search: prev => ({ ...prev, ...updates, page: 1 }), // 필터 변경 시 1페이지로
|
|
160
|
-
})
|
|
57
|
+
const updateSearch = (updates: Partial<z.infer<typeof searchSchema>>) => {
|
|
58
|
+
navigate({ to: '/posts', search: prev => ({ ...prev, ...updates, page: 1 }) })
|
|
161
59
|
}
|
|
162
60
|
|
|
163
61
|
return (
|
|
164
62
|
<div>
|
|
165
|
-
{
|
|
166
|
-
<
|
|
167
|
-
value={search}
|
|
168
|
-
onChange={e => updateSearch({ search: e.target.value })}
|
|
169
|
-
placeholder="Search..."
|
|
170
|
-
/>
|
|
171
|
-
|
|
172
|
-
{/* 카테고리 필터 */}
|
|
173
|
-
<select
|
|
174
|
-
value={category}
|
|
175
|
-
onChange={e => updateSearch({ category: e.target.value as any })}
|
|
176
|
-
>
|
|
63
|
+
<input value={search} onChange={e => updateSearch({ search: e.target.value })} />
|
|
64
|
+
<select value={category} onChange={e => updateSearch({ category: e.target.value as any })}>
|
|
177
65
|
<option value="all">All</option>
|
|
178
66
|
<option value="tech">Tech</option>
|
|
179
|
-
<option value="lifestyle">Lifestyle</option>
|
|
180
67
|
</select>
|
|
181
|
-
|
|
182
|
-
{/* 정렬 */}
|
|
183
|
-
<select
|
|
184
|
-
value={sort}
|
|
185
|
-
onChange={e => updateSearch({ sort: e.target.value as any })}
|
|
186
|
-
>
|
|
187
|
-
<option value="newest">Newest</option>
|
|
188
|
-
<option value="oldest">Oldest</option>
|
|
189
|
-
<option value="popular">Popular</option>
|
|
190
|
-
</select>
|
|
191
|
-
|
|
192
|
-
{/* 목록 */}
|
|
193
|
-
{posts.map(post => (
|
|
194
|
-
<div key={post.id}>{post.title}</div>
|
|
195
|
-
))}
|
|
68
|
+
{posts.map(post => <div key={post.id}>{post.title}</div>)}
|
|
196
69
|
</div>
|
|
197
70
|
)
|
|
198
71
|
}
|
|
199
72
|
```
|
|
200
73
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
Search params 변경 시 loader 재실행하려면 `loaderDeps` 필요.
|
|
204
|
-
|
|
205
|
-
```tsx
|
|
206
|
-
export const Route = createFileRoute('/products')({
|
|
207
|
-
validateSearch: searchSchema,
|
|
208
|
-
loaderDeps: ({ search }) => ({ search }), // 의존성 선언
|
|
209
|
-
loader: async ({ deps: { search } }) => {
|
|
210
|
-
return fetchProducts(search)
|
|
211
|
-
},
|
|
212
|
-
})
|
|
213
|
-
```
|
|
74
|
+
</patterns>
|