@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,19 +1,35 @@
|
|
|
1
1
|
# Custom Hook 패턴
|
|
2
2
|
|
|
3
|
-
페이지/섹션의 모든 로직, 상태,
|
|
3
|
+
> 페이지/섹션의 모든 로직, 상태, 라이프사이클 중앙화
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<instructions>
|
|
6
|
+
@../library/tanstack-router/hooks.md
|
|
7
|
+
@../library/tanstack-query/use-query.md
|
|
8
|
+
@../library/tanstack-query/use-mutation.md
|
|
9
|
+
</instructions>
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<hook_order>
|
|
14
|
+
|
|
15
|
+
## Hook 내부 순서 (필수)
|
|
16
|
+
|
|
17
|
+
| 순서 | Hook 타입 | 예시 |
|
|
18
|
+
|------|-----------|------|
|
|
19
|
+
| 1 | State | `useState`, zustand store |
|
|
20
|
+
| 2 | Global Hooks | `useParams`, `useNavigate`, `useQueryClient` |
|
|
21
|
+
| 3 | React Query | `useQuery` → `useMutation` |
|
|
22
|
+
| 4 | Event Handlers | `handleCreate`, `handleDelete` |
|
|
23
|
+
| 5 | useMemo | 계산된 값 |
|
|
24
|
+
| 6 | useEffect | 부수 효과 |
|
|
15
25
|
|
|
16
|
-
|
|
26
|
+
</hook_order>
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
<patterns>
|
|
31
|
+
|
|
32
|
+
## Page Hook 패턴
|
|
17
33
|
|
|
18
34
|
```typescript
|
|
19
35
|
// routes/users/-hooks/use-users.ts
|
|
@@ -28,13 +44,10 @@ interface UseUsersReturn {
|
|
|
28
44
|
users: User[] | undefined
|
|
29
45
|
filteredUsers: User[]
|
|
30
46
|
isLoading: boolean
|
|
31
|
-
error: Error | null
|
|
32
47
|
search: string
|
|
33
48
|
setSearch: (value: string) => void
|
|
34
49
|
handleCreate: (data: { email: string; name: string }) => void
|
|
35
50
|
handleDelete: (id: string) => void
|
|
36
|
-
isCreating: boolean
|
|
37
|
-
isDeleting: boolean
|
|
38
51
|
}
|
|
39
52
|
|
|
40
53
|
export const useUsers = (): UseUsersReturn => {
|
|
@@ -54,7 +67,7 @@ export const useUsers = (): UseUsersReturn => {
|
|
|
54
67
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
55
68
|
// 3. React Query
|
|
56
69
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
57
|
-
const { data: users, isLoading
|
|
70
|
+
const { data: users, isLoading } = useQuery({
|
|
58
71
|
queryKey: ['users'],
|
|
59
72
|
queryFn: () => getUsers(),
|
|
60
73
|
})
|
|
@@ -110,35 +123,15 @@ export const useUsers = (): UseUsersReturn => {
|
|
|
110
123
|
users,
|
|
111
124
|
filteredUsers,
|
|
112
125
|
isLoading,
|
|
113
|
-
error,
|
|
114
126
|
search,
|
|
115
127
|
setSearch,
|
|
116
128
|
handleCreate,
|
|
117
129
|
handleDelete,
|
|
118
|
-
isCreating: createMutation.isPending,
|
|
119
|
-
isDeleting: deleteMutation.isPending,
|
|
120
130
|
}
|
|
121
131
|
}
|
|
122
132
|
```
|
|
123
133
|
|
|
124
|
-
##
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
// ❌ 순서가 뒤섞인 잘못된 예시
|
|
128
|
-
export const useBadHook = () => {
|
|
129
|
-
const queryClient = useQueryClient() // ❌ Global Hook이 먼저
|
|
130
|
-
|
|
131
|
-
useEffect(() => { /* ... */ }, []) // ❌ useEffect가 중간에
|
|
132
|
-
|
|
133
|
-
const [state, setState] = useState() // ❌ State가 나중에
|
|
134
|
-
|
|
135
|
-
const { data } = useQuery({ /* ... */ }) // ❌ Query가 Effect 다음에
|
|
136
|
-
|
|
137
|
-
const computed = useMemo(() => {}, []) // ❌ useMemo 위치 잘못됨
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## Filter Hook 예시
|
|
134
|
+
## Filter Hook 패턴
|
|
142
135
|
|
|
143
136
|
```typescript
|
|
144
137
|
// routes/users/-hooks/use-user-filter.ts
|
|
@@ -164,3 +157,215 @@ export const useUserFilter = (): UseUserFilterReturn => {
|
|
|
164
157
|
return { search, setSearch, role, setRole, clearFilters }
|
|
165
158
|
}
|
|
166
159
|
```
|
|
160
|
+
|
|
161
|
+
## Form Hook 패턴
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// routes/users/-hooks/use-user-form.ts
|
|
165
|
+
import { useState } from 'react'
|
|
166
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
167
|
+
import { createUser } from '@/services/user'
|
|
168
|
+
import type { CreateUserInput } from '@/services/user'
|
|
169
|
+
|
|
170
|
+
interface UseUserFormReturn {
|
|
171
|
+
email: string
|
|
172
|
+
setEmail: (value: string) => void
|
|
173
|
+
name: string
|
|
174
|
+
setName: (value: string) => void
|
|
175
|
+
handleSubmit: (e: React.FormEvent) => void
|
|
176
|
+
isSubmitting: boolean
|
|
177
|
+
error: Error | null
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export const useUserForm = (): UseUserFormReturn => {
|
|
181
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
182
|
+
// 1. State
|
|
183
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
184
|
+
const [email, setEmail] = useState('')
|
|
185
|
+
const [name, setName] = useState('')
|
|
186
|
+
|
|
187
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
188
|
+
// 2. Global Hooks
|
|
189
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
190
|
+
const queryClient = useQueryClient()
|
|
191
|
+
|
|
192
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
193
|
+
// 3. React Query
|
|
194
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
195
|
+
const { mutate, isPending, error } = useMutation({
|
|
196
|
+
mutationFn: createUser,
|
|
197
|
+
onSuccess: () => {
|
|
198
|
+
queryClient.invalidateQueries({ queryKey: ['users'] })
|
|
199
|
+
setEmail('')
|
|
200
|
+
setName('')
|
|
201
|
+
},
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
205
|
+
// 4. Event Handlers
|
|
206
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
207
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
208
|
+
e.preventDefault()
|
|
209
|
+
mutate({ data: { email, name } })
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
email,
|
|
214
|
+
setEmail,
|
|
215
|
+
name,
|
|
216
|
+
setName,
|
|
217
|
+
handleSubmit,
|
|
218
|
+
isSubmitting: isPending,
|
|
219
|
+
error,
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
</patterns>
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
<anti_patterns>
|
|
229
|
+
|
|
230
|
+
## ❌ 잘못된 순서
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
// ❌ 순서가 뒤섞인 잘못된 예시
|
|
234
|
+
export const useBadHook = () => {
|
|
235
|
+
const queryClient = useQueryClient() // ❌ Global Hook이 먼저
|
|
236
|
+
|
|
237
|
+
useEffect(() => { /* ... */ }, []) // ❌ useEffect가 중간에
|
|
238
|
+
|
|
239
|
+
const [state, setState] = useState() // ❌ State가 나중에
|
|
240
|
+
|
|
241
|
+
const { data } = useQuery({ /* ... */ }) // ❌ Query가 Effect 다음에
|
|
242
|
+
|
|
243
|
+
const computed = useMemo(() => {}, []) // ❌ useMemo 위치 잘못됨
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## ✅ 올바른 순서
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
export const useGoodHook = () => {
|
|
251
|
+
// 1. State
|
|
252
|
+
const [state, setState] = useState()
|
|
253
|
+
|
|
254
|
+
// 2. Global Hooks
|
|
255
|
+
const queryClient = useQueryClient()
|
|
256
|
+
|
|
257
|
+
// 3. React Query
|
|
258
|
+
const { data } = useQuery({ /* ... */ })
|
|
259
|
+
|
|
260
|
+
// 4. Event Handlers
|
|
261
|
+
const handleClick = useCallback(() => {}, [])
|
|
262
|
+
|
|
263
|
+
// 5. useMemo
|
|
264
|
+
const computed = useMemo(() => {}, [])
|
|
265
|
+
|
|
266
|
+
// 6. useEffect
|
|
267
|
+
useEffect(() => { /* ... */ }, [])
|
|
268
|
+
|
|
269
|
+
return { state, data, handleClick, computed }
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
</anti_patterns>
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
<router_hooks>
|
|
278
|
+
|
|
279
|
+
## TanStack Router Hooks
|
|
280
|
+
|
|
281
|
+
| Hook | 용도 | 예시 |
|
|
282
|
+
|------|------|------|
|
|
283
|
+
| `useParams` | URL 파라미터 | `const { id } = useParams({ from: '/users/$id' })` |
|
|
284
|
+
| `useNavigate` | 프로그래밍 방식 네비게이션 | `navigate({ to: '/users' })` |
|
|
285
|
+
| `useSearch` | Search params | `const { page } = useSearch({ from: '/users' })` |
|
|
286
|
+
| `useLoaderData` | Loader 데이터 | `const user = Route.useLoaderData()` |
|
|
287
|
+
| `useRouteContext` | Route context | `const { auth } = useRouteContext({ from: '__root__' })` |
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
// routes/users/$id.tsx Hook
|
|
291
|
+
import { useParams, useNavigate, useLoaderData } from '@tanstack/react-router'
|
|
292
|
+
|
|
293
|
+
export const useUserDetail = () => {
|
|
294
|
+
const { id } = useParams({ from: '/users/$id' })
|
|
295
|
+
const navigate = useNavigate()
|
|
296
|
+
const user = Route.useLoaderData()
|
|
297
|
+
|
|
298
|
+
const handleBack = () => navigate({ to: '/users' })
|
|
299
|
+
const handleEdit = () => navigate({ to: '/users/$id/edit', params: { id } })
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
user,
|
|
303
|
+
handleBack,
|
|
304
|
+
handleEdit,
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
</router_hooks>
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
<query_hooks>
|
|
314
|
+
|
|
315
|
+
## TanStack Query Hooks
|
|
316
|
+
|
|
317
|
+
| Hook | 용도 | 특징 |
|
|
318
|
+
|------|------|------|
|
|
319
|
+
| `useQuery` | 데이터 조회 (GET) | 자동 캐싱, 재검증 |
|
|
320
|
+
| `useMutation` | 데이터 변경 (POST/PUT/DELETE) | 성공 시 캐시 무효화 |
|
|
321
|
+
| `useQueryClient` | 캐시 제어 | `invalidateQueries`, `setQueryData` |
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
325
|
+
import { getUsers, createUser } from '@/services/user'
|
|
326
|
+
|
|
327
|
+
export const useUsers = () => {
|
|
328
|
+
const queryClient = useQueryClient()
|
|
329
|
+
|
|
330
|
+
const { data, isLoading, error } = useQuery({
|
|
331
|
+
queryKey: ['users'],
|
|
332
|
+
queryFn: () => getUsers(),
|
|
333
|
+
staleTime: 60 * 1000, // 1분
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
const { mutate, isPending } = useMutation({
|
|
337
|
+
mutationFn: createUser,
|
|
338
|
+
onSuccess: () => {
|
|
339
|
+
queryClient.invalidateQueries({ queryKey: ['users'] })
|
|
340
|
+
},
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
return { data, isLoading, error, mutate, isPending }
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
</query_hooks>
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
<best_practices>
|
|
352
|
+
|
|
353
|
+
| 원칙 | 설명 |
|
|
354
|
+
|------|------|
|
|
355
|
+
| **순서 준수** | State → Global → Query → Handlers → Memo → Effect |
|
|
356
|
+
| **타입 정의** | Return 타입 명시 (interface) |
|
|
357
|
+
| **단일 책임** | 하나의 Hook은 하나의 관심사 |
|
|
358
|
+
| **useCallback** | Event handler는 useCallback으로 메모이제이션 |
|
|
359
|
+
| **명확한 네이밍** | `use-users.ts`, `use-user-filter.ts` |
|
|
360
|
+
|
|
361
|
+
</best_practices>
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
<sources>
|
|
366
|
+
|
|
367
|
+
- [TanStack Router Hooks](https://tanstack.com/router/latest/docs/framework/react/guide/router-hooks)
|
|
368
|
+
- [TanStack Query useQuery](https://tanstack.com/query/latest/docs/framework/react/guides/queries)
|
|
369
|
+
- [TanStack Query useMutation](https://tanstack.com/query/latest/docs/framework/react/guides/mutations)
|
|
370
|
+
|
|
371
|
+
</sources>
|
|
@@ -1,38 +1,63 @@
|
|
|
1
1
|
# 라우트 구조
|
|
2
2
|
|
|
3
|
-
TanStack Start 파일 기반 라우팅
|
|
3
|
+
> TanStack Start 파일 기반 라우팅
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<instructions>
|
|
6
|
+
@../library/tanstack-router/index.md
|
|
7
|
+
</instructions>
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
routes/<route-name>/
|
|
9
|
-
├── index.tsx # 페이지 컴포넌트
|
|
10
|
-
├── route.tsx # route 설정 (필요시)
|
|
11
|
-
├── -components/ # 페이지 전용 컴포넌트
|
|
12
|
-
│ ├── user-card.tsx
|
|
13
|
-
│ └── user-form.tsx
|
|
14
|
-
├── -sections/ # 섹션 분리 (복잡한 경우)
|
|
15
|
-
│ ├── user-list-section.tsx
|
|
16
|
-
│ └── user-filter-section.tsx
|
|
17
|
-
└── -hooks/ # 페이지 전용 훅
|
|
18
|
-
├── use-users.ts
|
|
19
|
-
└── use-user-filter.ts
|
|
20
|
-
```
|
|
9
|
+
---
|
|
21
10
|
|
|
22
|
-
|
|
11
|
+
<route_structure>
|
|
23
12
|
|
|
24
|
-
|
|
13
|
+
## 라우트 폴더 구조
|
|
25
14
|
|
|
26
15
|
```
|
|
27
|
-
routes/
|
|
28
|
-
├──
|
|
29
|
-
├──
|
|
30
|
-
├──
|
|
31
|
-
├──
|
|
32
|
-
|
|
16
|
+
routes/
|
|
17
|
+
├── __root.tsx # Root Layout
|
|
18
|
+
├── index.tsx # / (Home)
|
|
19
|
+
├── users/
|
|
20
|
+
│ ├── index.tsx # /users (List)
|
|
21
|
+
│ ├── $id.tsx # /users/:id (Detail)
|
|
22
|
+
│ ├── -components/ # 페이지 전용 컴포넌트
|
|
23
|
+
│ ├── -sections/ # 섹션 분리 (복잡한 경우)
|
|
24
|
+
│ └── -hooks/ # 페이지 전용 Hook
|
|
25
|
+
└── posts/
|
|
26
|
+
├── index.tsx
|
|
27
|
+
└── $slug.tsx
|
|
33
28
|
```
|
|
34
29
|
|
|
35
|
-
|
|
30
|
+
| 접두사 | 용도 | 라우트 생성 |
|
|
31
|
+
|--------|------|-----------|
|
|
32
|
+
| `-` | 라우트 제외 폴더 | ❌ 제외 |
|
|
33
|
+
| `$` | 동적 파라미터 | ✅ 생성 |
|
|
34
|
+
| `_` | Pathless Layout | ✅ 생성 (경로 없음) |
|
|
35
|
+
|
|
36
|
+
</route_structure>
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
<file_naming>
|
|
41
|
+
|
|
42
|
+
## 라우트 파일명 규칙
|
|
43
|
+
|
|
44
|
+
| 경로 | 파일명 | 설명 |
|
|
45
|
+
|------|--------|------|
|
|
46
|
+
| `/` | `index.tsx` | 인덱스 라우트 |
|
|
47
|
+
| `/users` | `users/index.tsx` | 목록 페이지 |
|
|
48
|
+
| `/users/:id` | `users/$id.tsx` | 동적 파라미터 |
|
|
49
|
+
| `/posts/:slug` | `posts/$slug.tsx` | URL 파라미터 |
|
|
50
|
+
| `/dashboard/*` | `dashboard/$.tsx` | Catch-all 라우트 |
|
|
51
|
+
| Layout | `__root.tsx` | Root 레이아웃 |
|
|
52
|
+
| Pathless | `_layout.tsx` | 경로 없는 레이아웃 |
|
|
53
|
+
|
|
54
|
+
</file_naming>
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
<patterns>
|
|
59
|
+
|
|
60
|
+
## 기본 라우트 패턴
|
|
36
61
|
|
|
37
62
|
```tsx
|
|
38
63
|
// routes/users/index.tsx
|
|
@@ -55,6 +80,105 @@ const UsersPage = (): JSX.Element => {
|
|
|
55
80
|
}
|
|
56
81
|
```
|
|
57
82
|
|
|
83
|
+
## 동적 라우트 + Loader
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// routes/users/$id.tsx
|
|
87
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
88
|
+
import { getUserById } from '@/services/user'
|
|
89
|
+
|
|
90
|
+
export const Route = createFileRoute('/users/$id')({
|
|
91
|
+
loader: ({ params: { id } }) => getUserById({ data: id }),
|
|
92
|
+
component: UserDetailPage,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const UserDetailPage = (): JSX.Element => {
|
|
96
|
+
const user = Route.useLoaderData()
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div>
|
|
100
|
+
<h1>{user.name}</h1>
|
|
101
|
+
<p>{user.email}</p>
|
|
102
|
+
</div>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Loader with Dependencies
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
// routes/posts/index.tsx
|
|
111
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
112
|
+
import { getPosts } from '@/services/post'
|
|
113
|
+
import { z } from 'zod'
|
|
114
|
+
|
|
115
|
+
const postsSearchSchema = z.object({
|
|
116
|
+
offset: z.number().default(0),
|
|
117
|
+
limit: z.number().default(10),
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
export const Route = createFileRoute('/posts/')({
|
|
121
|
+
validateSearch: postsSearchSchema,
|
|
122
|
+
loaderDeps: ({ search: { offset, limit } }) => ({ offset, limit }),
|
|
123
|
+
loader: ({ deps: { offset, limit } }) => getPosts({ data: { offset, limit } }),
|
|
124
|
+
component: PostsPage,
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const PostsPage = (): JSX.Element => {
|
|
128
|
+
const posts = Route.useLoaderData()
|
|
129
|
+
const { offset, limit } = Route.useSearch()
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<div>
|
|
133
|
+
<h1>Posts (offset: {offset}, limit: {limit})</h1>
|
|
134
|
+
{posts.map((post) => (
|
|
135
|
+
<div key={post.id}>{post.title}</div>
|
|
136
|
+
))}
|
|
137
|
+
</div>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Deferred Data Loading
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
// routes/dashboard/index.tsx
|
|
146
|
+
import { createFileRoute, Await } from '@tanstack/react-router'
|
|
147
|
+
import { Suspense } from 'react'
|
|
148
|
+
import { getFastData, getSlowData } from '@/services/dashboard'
|
|
149
|
+
|
|
150
|
+
export const Route = createFileRoute('/dashboard/')({
|
|
151
|
+
loader: async () => {
|
|
152
|
+
const slowDataPromise = getSlowData()
|
|
153
|
+
const fastData = await getFastData()
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
fastData,
|
|
157
|
+
deferredSlowData: slowDataPromise,
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
component: DashboardPage,
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
const DashboardPage = (): JSX.Element => {
|
|
164
|
+
const { fastData, deferredSlowData } = Route.useLoaderData()
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<div>
|
|
168
|
+
<h1>Fast Data</h1>
|
|
169
|
+
<pre>{JSON.stringify(fastData, null, 2)}</pre>
|
|
170
|
+
|
|
171
|
+
<h1>Slow Data (Deferred)</h1>
|
|
172
|
+
<Suspense fallback={<div>Loading slow data...</div>}>
|
|
173
|
+
<Await promise={deferredSlowData}>
|
|
174
|
+
{(slowData) => <pre>{JSON.stringify(slowData, null, 2)}</pre>}
|
|
175
|
+
</Await>
|
|
176
|
+
</Suspense>
|
|
177
|
+
</div>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
58
182
|
## Section 패턴
|
|
59
183
|
|
|
60
184
|
```tsx
|
|
@@ -84,42 +208,6 @@ export const UserListSection = (): JSX.Element => {
|
|
|
84
208
|
}
|
|
85
209
|
```
|
|
86
210
|
|
|
87
|
-
## Filter Section 패턴
|
|
88
|
-
|
|
89
|
-
```tsx
|
|
90
|
-
// routes/users/-sections/user-filter-section.tsx
|
|
91
|
-
import { useUserFilter } from '../-hooks/use-user-filter'
|
|
92
|
-
import { Input } from '@/components/ui/input'
|
|
93
|
-
import { Button } from '@/components/ui/button'
|
|
94
|
-
|
|
95
|
-
export const UserFilterSection = (): JSX.Element => {
|
|
96
|
-
const { search, setSearch, role, setRole, clearFilters } = useUserFilter()
|
|
97
|
-
|
|
98
|
-
return (
|
|
99
|
-
<div className="flex gap-4 mb-6">
|
|
100
|
-
<Input
|
|
101
|
-
placeholder="Search users..."
|
|
102
|
-
value={search}
|
|
103
|
-
onChange={(e) => setSearch(e.target.value)}
|
|
104
|
-
className="max-w-xs"
|
|
105
|
-
/>
|
|
106
|
-
<select
|
|
107
|
-
value={role}
|
|
108
|
-
onChange={(e) => setRole(e.target.value)}
|
|
109
|
-
className="border rounded px-3 py-2"
|
|
110
|
-
>
|
|
111
|
-
<option value="">All Roles</option>
|
|
112
|
-
<option value="USER">User</option>
|
|
113
|
-
<option value="ADMIN">Admin</option>
|
|
114
|
-
</select>
|
|
115
|
-
<Button variant="outline" onClick={clearFilters}>
|
|
116
|
-
Clear
|
|
117
|
-
</Button>
|
|
118
|
-
</div>
|
|
119
|
-
)
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
211
|
## 컴포넌트 패턴
|
|
124
212
|
|
|
125
213
|
```tsx
|
|
@@ -164,3 +252,67 @@ export const UserCard = ({
|
|
|
164
252
|
)
|
|
165
253
|
}
|
|
166
254
|
```
|
|
255
|
+
|
|
256
|
+
</patterns>
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
<page_size_rules>
|
|
261
|
+
|
|
262
|
+
| 페이지 크기 | 구조 | 예시 |
|
|
263
|
+
|------------|------|------|
|
|
264
|
+
| ~100줄 | 단일 파일 | 간단한 페이지, 폼 |
|
|
265
|
+
| 100-200줄 | `-components/` 분리 | 목록 + 필터 |
|
|
266
|
+
| 200줄+ | `-sections/` + `-components/` | 대시보드, 복잡한 UI |
|
|
267
|
+
|
|
268
|
+
</page_size_rules>
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
<loader_execution>
|
|
273
|
+
|
|
274
|
+
## Loader 실행 순서
|
|
275
|
+
|
|
276
|
+
| 단계 | 실행 방식 | 설명 |
|
|
277
|
+
|------|----------|------|
|
|
278
|
+
| 1. `beforeLoad` | 순차 (outermost → innermost) | 인증 체크, 컨텍스트 설정 |
|
|
279
|
+
| 2. `loader` | 병렬 (모든 loader 동시) | 데이터 페칭 |
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
// Parent Route
|
|
283
|
+
export const Route = createFileRoute('/dashboard')({
|
|
284
|
+
beforeLoad: async () => {
|
|
285
|
+
// 1. 먼저 실행 (순차)
|
|
286
|
+
const auth = await checkAuth()
|
|
287
|
+
return { auth }
|
|
288
|
+
},
|
|
289
|
+
loader: async () => {
|
|
290
|
+
// 2. 나중에 실행 (병렬)
|
|
291
|
+
return getDashboardData()
|
|
292
|
+
},
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
// Child Route
|
|
296
|
+
export const Route = createFileRoute('/dashboard/users')({
|
|
297
|
+
beforeLoad: async () => {
|
|
298
|
+
// 1. Parent beforeLoad 다음 실행
|
|
299
|
+
return {}
|
|
300
|
+
},
|
|
301
|
+
loader: async () => {
|
|
302
|
+
// 2. Parent loader와 병렬 실행
|
|
303
|
+
return getUsers()
|
|
304
|
+
},
|
|
305
|
+
})
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
</loader_execution>
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
<sources>
|
|
313
|
+
|
|
314
|
+
- [TanStack Router Data Loading](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading)
|
|
315
|
+
- [TanStack Router Deferred Data Loading](https://tanstack.com/router/latest/docs/framework/react/guide/deferred-data-loading)
|
|
316
|
+
- [TanStack Router File-Based Routing](https://tanstack.com/router/latest/docs/framework/react/guide/file-based-routing)
|
|
317
|
+
|
|
318
|
+
</sources>
|