@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.
Files changed (130) hide show
  1. package/dist/index.js +13 -8
  2. package/package.json +1 -1
  3. package/templates/.claude/agents/code-reviewer.md +371 -19
  4. package/templates/.claude/agents/dependency-manager.md +197 -0
  5. package/templates/.claude/agents/deployment-validator.md +136 -0
  6. package/templates/.claude/agents/git-operator.md +147 -0
  7. package/templates/.claude/agents/implementation-executor.md +202 -0
  8. package/templates/.claude/agents/lint-fixer.md +155 -0
  9. package/templates/.claude/agents/refactor-advisor.md +339 -29
  10. package/templates/.claude/commands/agent-creator.md +355 -0
  11. package/templates/.claude/commands/docs-creator.md +404 -163
  12. package/templates/.claude/commands/docs-refactor.md +400 -113
  13. package/templates/.claude/commands/execute.md +357 -185
  14. package/templates/.claude/commands/git-all.md +65 -71
  15. package/templates/.claude/commands/git-session.md +80 -64
  16. package/templates/.claude/commands/git.md +68 -72
  17. package/templates/.claude/commands/lint-fix.md +224 -109
  18. package/templates/.claude/commands/lint-init.md +142 -168
  19. package/templates/.claude/commands/plan.md +300 -84
  20. package/templates/.claude/commands/prd.md +497 -214
  21. package/templates/.claude/commands/pre-deploy.md +242 -0
  22. package/templates/.claude/commands/subagent-creator.md +118 -0
  23. package/templates/.claude/commands/version-update.md +45 -57
  24. package/templates/hono/CLAUDE.md +99 -54
  25. package/templates/hono/docs/guides/conventions.md +352 -0
  26. package/templates/hono/docs/guides/env-setup.md +347 -0
  27. package/templates/hono/docs/guides/getting-started.md +239 -0
  28. package/templates/hono/docs/library/hono/error-handling.md +20 -29
  29. package/templates/hono/docs/library/hono/index.md +25 -52
  30. package/templates/hono/docs/library/hono/middleware.md +16 -75
  31. package/templates/hono/docs/library/hono/rpc.md +7 -35
  32. package/templates/hono/docs/library/hono/validation.md +25 -45
  33. package/templates/hono/docs/library/t3-env/index.md +374 -0
  34. package/templates/npx/CLAUDE.md +165 -65
  35. package/templates/npx/docs/library/commander/index.md +10 -73
  36. package/templates/npx/docs/library/fs-extra/index.md +21 -113
  37. package/templates/npx/docs/library/prompts/index.md +30 -176
  38. package/templates/npx/docs/references/patterns.md +75 -48
  39. package/templates/tanstack-start/CLAUDE.md +101 -77
  40. package/templates/tanstack-start/docs/architecture.md +427 -0
  41. package/templates/tanstack-start/docs/design.md +558 -0
  42. package/templates/tanstack-start/docs/guides/conventions.md +132 -32
  43. package/templates/tanstack-start/docs/guides/env-setup.md +127 -62
  44. package/templates/tanstack-start/docs/guides/getting-started.md +81 -20
  45. package/templates/tanstack-start/docs/guides/hooks.md +241 -36
  46. package/templates/tanstack-start/docs/guides/routes.md +213 -61
  47. package/templates/tanstack-start/docs/guides/services.md +260 -24
  48. package/templates/tanstack-start/docs/library/better-auth/index.md +469 -16
  49. package/templates/tanstack-start/docs/library/t3-env/index.md +307 -0
  50. package/templates/tanstack-start/docs/library/tanstack-query/index.md +12 -21
  51. package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +22 -35
  52. package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +7 -24
  53. package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +26 -39
  54. package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +23 -26
  55. package/templates/tanstack-start/docs/library/tanstack-router/error-handling.md +32 -147
  56. package/templates/tanstack-start/docs/library/tanstack-router/hooks.md +25 -167
  57. package/templates/tanstack-start/docs/library/tanstack-router/index.md +39 -74
  58. package/templates/tanstack-start/docs/library/tanstack-router/navigation.md +46 -116
  59. package/templates/tanstack-start/docs/library/tanstack-router/route-context.md +35 -154
  60. package/templates/tanstack-start/docs/library/tanstack-router/search-params.md +32 -171
  61. package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +7 -15
  62. package/templates/tanstack-start/docs/library/tanstack-start/routing.md +16 -23
  63. package/templates/tanstack-start/docs/library/zod/complex-types.md +12 -31
  64. package/templates/tanstack-start/docs/library/zod/index.md +18 -35
  65. package/templates/tanstack-start/docs/library/zod/transforms.md +11 -25
  66. package/templates/tanstack-start/docs/library/zod/validation.md +12 -34
  67. package/templates/.claude/agents/debug-detective.md +0 -37
  68. package/templates/.claude/agents/test-writer.md +0 -41
  69. package/templates/.claude/commands/feedback.md +0 -199
  70. package/templates/.claude/commands/ts-fix.md +0 -176
  71. package/templates/.claude/skills/command-creator/LICENSE.txt +0 -202
  72. package/templates/.claude/skills/command-creator/SKILL.md +0 -245
  73. package/templates/.claude/skills/command-creator/scripts/init_command.py +0 -244
  74. package/templates/.claude/skills/command-creator/scripts/package_command.py +0 -125
  75. package/templates/.claude/skills/command-creator/scripts/quick_validate.py +0 -143
  76. package/templates/.claude/skills/frontend-design/SKILL.md +0 -310
  77. package/templates/.claude/skills/frontend-design/references/animation-patterns.md +0 -446
  78. package/templates/.claude/skills/frontend-design/references/colors-2026.md +0 -244
  79. package/templates/.claude/skills/frontend-design/references/typography-2026.md +0 -302
  80. package/templates/.claude/skills/gemini-review/SKILL.md +0 -118
  81. package/templates/.claude/skills/gemini-review/references/checklists.md +0 -129
  82. package/templates/.claude/skills/gemini-review/references/prompt-templates.md +0 -274
  83. package/templates/.claude/skills/skill-creator/LICENSE.txt +0 -202
  84. package/templates/.claude/skills/skill-creator/SKILL.md +0 -184
  85. package/templates/.claude/skills/skill-creator/scripts/init_skill.py +0 -303
  86. package/templates/.claude/skills/skill-creator/scripts/package_skill.py +0 -110
  87. package/templates/.claude/skills/skill-creator/scripts/quick_validate.py +0 -65
  88. package/templates/hono/docs/library/ai-sdk/index.md +0 -190
  89. package/templates/hono/docs/library/ai-sdk/openrouter.md +0 -111
  90. package/templates/hono/docs/library/ai-sdk/providers.md +0 -102
  91. package/templates/hono/docs/library/ai-sdk/streaming.md +0 -146
  92. package/templates/hono/docs/library/ai-sdk/structured-output.md +0 -161
  93. package/templates/hono/docs/library/ai-sdk/tools.md +0 -144
  94. package/templates/hono/docs/library/drizzle/cloudflare-d1.md +0 -247
  95. package/templates/hono/docs/library/drizzle/config.md +0 -167
  96. package/templates/hono/docs/library/drizzle/index.md +0 -259
  97. package/templates/hono/docs/library/hono/env-setup.md +0 -169
  98. package/templates/hono/docs/library/pino/index.md +0 -146
  99. package/templates/tanstack-start/docs/architecture/architecture.md +0 -243
  100. package/templates/tanstack-start/docs/deployment/cloudflare.md +0 -132
  101. package/templates/tanstack-start/docs/deployment/index.md +0 -163
  102. package/templates/tanstack-start/docs/deployment/nitro.md +0 -110
  103. package/templates/tanstack-start/docs/deployment/railway.md +0 -147
  104. package/templates/tanstack-start/docs/deployment/vercel.md +0 -135
  105. package/templates/tanstack-start/docs/design/components.md +0 -175
  106. package/templates/tanstack-start/docs/design/index.md +0 -151
  107. package/templates/tanstack-start/docs/design/safe-area.md +0 -118
  108. package/templates/tanstack-start/docs/design/tailwind-setup.md +0 -156
  109. package/templates/tanstack-start/docs/library/ai-sdk/hooks.md +0 -472
  110. package/templates/tanstack-start/docs/library/ai-sdk/index.md +0 -264
  111. package/templates/tanstack-start/docs/library/ai-sdk/openrouter.md +0 -371
  112. package/templates/tanstack-start/docs/library/ai-sdk/providers.md +0 -403
  113. package/templates/tanstack-start/docs/library/ai-sdk/streaming.md +0 -320
  114. package/templates/tanstack-start/docs/library/ai-sdk/structured-output.md +0 -454
  115. package/templates/tanstack-start/docs/library/ai-sdk/tools.md +0 -473
  116. package/templates/tanstack-start/docs/library/better-auth/2fa.md +0 -48
  117. package/templates/tanstack-start/docs/library/better-auth/advanced.md +0 -55
  118. package/templates/tanstack-start/docs/library/better-auth/plugins.md +0 -34
  119. package/templates/tanstack-start/docs/library/better-auth/session.md +0 -47
  120. package/templates/tanstack-start/docs/library/better-auth/setup.md +0 -41
  121. package/templates/tanstack-start/docs/library/drizzle/cloudflare-d1.md +0 -147
  122. package/templates/tanstack-start/docs/library/drizzle/config.md +0 -118
  123. package/templates/tanstack-start/docs/library/drizzle/crud.md +0 -205
  124. package/templates/tanstack-start/docs/library/drizzle/index.md +0 -79
  125. package/templates/tanstack-start/docs/library/drizzle/relations.md +0 -202
  126. package/templates/tanstack-start/docs/library/drizzle/schema.md +0 -154
  127. package/templates/tanstack-start/docs/library/drizzle/setup.md +0 -96
  128. package/templates/tanstack-start/docs/library/drizzle/transactions.md +0 -127
  129. package/templates/tanstack-start/docs/library/pino/index.md +0 -320
  130. /package/templates/hono/docs/{architecture/architecture.md → architecture.md} +0 -0
@@ -1,29 +1,13 @@
1
1
  # TanStack Router - Navigation
2
2
 
3
- Link 컴포넌트와 프로그래밍 네비게이션.
4
-
5
- ## Link 컴포넌트
3
+ <patterns>
6
4
 
7
5
  ```tsx
8
- import { Link } from '@tanstack/react-router'
9
-
10
- // 기본
6
+ // Link
11
7
  <Link to="/about">About</Link>
12
-
13
- // 동적 파라미터
14
- <Link to="/posts/$postId" params={{ postId: '123' }}>
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
- // 정확히 일치할 때만 active
38
- <Link to="/" activeOptions={{ exact: true }}>
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 goToAbout = () => {
66
- navigate({ to: '/about' })
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
- const goToPost = (postId: string) => {
71
- navigate({ to: '/posts/$postId', params: { postId } })
72
- }
32
+ return <button onClick={goToAbout}>Go</button>
33
+ }
73
34
 
74
- // Search params
75
- const updateSearch = () => {
76
- navigate({
77
- to: '/products',
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
- // Replace (뒤로가기 안 됨)
83
- const replaceRoute = () => {
84
- navigate({ to: '/login', replace: true })
85
- }
40
+ // 조건부 네비게이션
41
+ const SubmitButton = () => {
42
+ const navigate = useNavigate()
43
+ const [isPending, startTransition] = useTransition()
86
44
 
87
- // 상대 경로
88
- const goUp = () => {
89
- navigate({ to: '..' })
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
- ## navigate 옵션
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
- ## Preloading
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
- beforeLoad, context, protected routes 구현.
4
-
5
- ## beforeLoad
6
-
7
- 라우트 로드 전 실행. 인증 체크, 리다이렉트, context 추가.
3
+ <patterns>
8
4
 
9
5
  ```tsx
10
- import { createFileRoute, redirect } from '@tanstack/react-router'
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
- function DashboardPage() {
75
- // _authed에서 전달된 context
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
- function RootLayout() {
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
- // Search params 포함
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
- // replace (뒤로가기 X)
172
- throw redirect({
173
- to: '/home',
174
- replace: true,
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
- ## Context 사용 위치
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
- ```tsx
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
- Type-safe URL search params with Zod validation.
4
-
5
- ## 기본 사용
3
+ <patterns>
6
4
 
7
5
  ```tsx
8
- import { createFileRoute } from '@tanstack/react-router'
9
- import { z } from 'zod'
10
-
11
- // 스키마 정의
12
- const productSearchSchema = z.object({
13
- page: z.number().catch(1),
14
- filter: z.string().catch(''),
15
- sort: z.enum(['newest', 'oldest', 'price']).catch('newest'),
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: productSearchSchema,
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
- function ProductsPage() {
28
- const { page, filter, sort } = Route.useSearch()
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={prev => ({ ...prev, page: 2 })}>
84
- Next Page
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
- <Link
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
- function PostsPage() {
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._type>) => {
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
- <input
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
- ## loaderDeps
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>