@kood/claude-code 0.1.6 → 0.1.7
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 +21 -243
- package/package.json +1 -1
- package/templates/hono/CLAUDE.md +10 -6
- package/templates/hono/docs/deployment/index.md +5 -0
- package/templates/hono/docs/library/hono/index.md +6 -0
- package/templates/hono/docs/library/prisma/index.md +3 -0
- package/templates/npx/CLAUDE.md +8 -2
- package/templates/tanstack-start/CLAUDE.md +103 -255
- package/templates/tanstack-start/docs/deployment/cloudflare.md +37 -424
- package/templates/tanstack-start/docs/deployment/index.md +57 -286
- package/templates/tanstack-start/docs/deployment/nitro.md +36 -318
- package/templates/tanstack-start/docs/deployment/railway.md +40 -409
- package/templates/tanstack-start/docs/deployment/vercel.md +43 -465
- package/templates/tanstack-start/docs/design/accessibility.md +56 -326
- package/templates/tanstack-start/docs/design/color.md +37 -179
- package/templates/tanstack-start/docs/design/components.md +77 -311
- package/templates/tanstack-start/docs/design/index.md +24 -87
- package/templates/tanstack-start/docs/design/safe-area.md +51 -250
- package/templates/tanstack-start/docs/design/spacing.md +57 -276
- package/templates/tanstack-start/docs/design/tailwind-setup.md +45 -359
- package/templates/tanstack-start/docs/design/typography.md +40 -284
- package/templates/tanstack-start/docs/library/better-auth/2fa.md +27 -115
- package/templates/tanstack-start/docs/library/better-auth/advanced.md +22 -105
- package/templates/tanstack-start/docs/library/better-auth/index.md +17 -66
- package/templates/tanstack-start/docs/library/better-auth/plugins.md +11 -88
- package/templates/tanstack-start/docs/library/better-auth/session.md +12 -92
- package/templates/tanstack-start/docs/library/better-auth/setup.md +9 -91
- package/templates/tanstack-start/docs/library/prisma/cloudflare-d1.md +30 -358
- package/templates/tanstack-start/docs/library/prisma/config.md +27 -327
- package/templates/tanstack-start/docs/library/prisma/crud.md +46 -174
- package/templates/tanstack-start/docs/library/prisma/index.md +23 -113
- package/templates/tanstack-start/docs/library/prisma/relations.md +31 -153
- package/templates/tanstack-start/docs/library/prisma/schema.md +40 -217
- package/templates/tanstack-start/docs/library/prisma/setup.md +12 -112
- package/templates/tanstack-start/docs/library/prisma/transactions.md +20 -110
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +12 -99
- package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +28 -107
- package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +44 -146
- package/templates/tanstack-start/docs/library/tanstack-query/setup.md +11 -70
- package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +33 -127
- package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +49 -149
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +19 -112
- package/templates/tanstack-start/docs/library/tanstack-start/index.md +33 -80
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +28 -106
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +21 -118
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +34 -246
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +6 -39
- package/templates/tanstack-start/docs/library/zod/basic-types.md +33 -145
- package/templates/tanstack-start/docs/library/zod/complex-types.md +32 -156
- package/templates/tanstack-start/docs/library/zod/index.md +22 -150
- package/templates/tanstack-start/docs/library/zod/transforms.md +20 -129
- package/templates/tanstack-start/docs/library/zod/validation.md +39 -155
- package/templates/hono/docs/commands/git.md +0 -145
- package/templates/hono/docs/mcp/context7.md +0 -106
- package/templates/hono/docs/mcp/index.md +0 -176
- package/templates/hono/docs/mcp/sequential-thinking.md +0 -101
- package/templates/hono/docs/mcp/serena.md +0 -269
- package/templates/hono/docs/mcp/sgrep.md +0 -105
- package/templates/hono/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/hono/docs/skills/gemini-review/references/checklists.md +0 -136
- package/templates/hono/docs/skills/gemini-review/references/prompt-templates.md +0 -303
- package/templates/npx/docs/commands/git.md +0 -145
- package/templates/npx/docs/mcp/index.md +0 -60
- package/templates/npx/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/npx/docs/skills/gemini-review/references/checklists.md +0 -134
- package/templates/npx/docs/skills/gemini-review/references/prompt-templates.md +0 -301
- package/templates/tanstack-start/docs/commands/git.md +0 -145
- package/templates/tanstack-start/docs/mcp/context7.md +0 -204
- package/templates/tanstack-start/docs/mcp/index.md +0 -177
- package/templates/tanstack-start/docs/mcp/sequential-thinking.md +0 -180
- package/templates/tanstack-start/docs/mcp/serena.md +0 -269
- package/templates/tanstack-start/docs/mcp/sgrep.md +0 -174
- package/templates/tanstack-start/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/tanstack-start/docs/skills/gemini-review/references/checklists.md +0 -144
- package/templates/tanstack-start/docs/skills/gemini-review/references/prompt-templates.md +0 -292
|
@@ -1,173 +1,73 @@
|
|
|
1
1
|
# TanStack Query - useQuery
|
|
2
2
|
|
|
3
|
-
> **상위 문서**: [TanStack Query](./index.md)
|
|
4
|
-
|
|
5
3
|
## 기본 사용법
|
|
6
4
|
|
|
7
5
|
```tsx
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
queryKey: ['todos'],
|
|
13
|
-
queryFn: getTodos,
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
if (isLoading) return <div>Loading...</div>
|
|
17
|
-
if (error) return <div>Error: {error.message}</div>
|
|
6
|
+
const { data, isLoading, error } = useQuery({
|
|
7
|
+
queryKey: ['todos'],
|
|
8
|
+
queryFn: getTodos,
|
|
9
|
+
})
|
|
18
10
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
{data?.map((todo) => (
|
|
22
|
-
<li key={todo.id}>{todo.title}</li>
|
|
23
|
-
))}
|
|
24
|
-
</ul>
|
|
25
|
-
)
|
|
26
|
-
}
|
|
11
|
+
if (isLoading) return <div>Loading...</div>
|
|
12
|
+
if (error) return <div>Error: {error.message}</div>
|
|
27
13
|
```
|
|
28
14
|
|
|
29
|
-
##
|
|
15
|
+
## 반환값
|
|
30
16
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
isStale, // 데이터가 stale 상태
|
|
41
|
-
refetch, // 수동 리페치 함수
|
|
42
|
-
status, // 'pending' | 'error' | 'success'
|
|
43
|
-
fetchStatus, // 'fetching' | 'paused' | 'idle'
|
|
44
|
-
} = useQuery({ queryKey, queryFn })
|
|
45
|
-
```
|
|
17
|
+
| 속성 | 설명 |
|
|
18
|
+
|------|------|
|
|
19
|
+
| data | 쿼리 결과 |
|
|
20
|
+
| error | 에러 객체 |
|
|
21
|
+
| isLoading | 첫 로딩 중 |
|
|
22
|
+
| isFetching | 백그라운드 페칭 중 |
|
|
23
|
+
| isError/isSuccess | 상태 |
|
|
24
|
+
| refetch | 수동 리페치 |
|
|
25
|
+
| status | 'pending' \| 'error' \| 'success' |
|
|
46
26
|
|
|
47
|
-
##
|
|
27
|
+
## 주요 옵션
|
|
48
28
|
|
|
49
29
|
```tsx
|
|
50
|
-
|
|
30
|
+
useQuery({
|
|
51
31
|
queryKey: ['todos'],
|
|
52
32
|
queryFn: fetchTodos,
|
|
53
|
-
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
refetchInterval: 1000 * 60, // 1분마다 리페치
|
|
62
|
-
|
|
63
|
-
// 재시도 관련
|
|
64
|
-
retry: 3, // 실패 시 재시도 횟수
|
|
65
|
-
retryDelay: 1000, // 재시도 딜레이
|
|
66
|
-
|
|
67
|
-
// 조건부 실행
|
|
68
|
-
enabled: !!userId, // false면 쿼리 실행 안 함
|
|
69
|
-
|
|
70
|
-
// 초기 데이터
|
|
71
|
-
initialData: [], // 초기 데이터
|
|
72
|
-
|
|
73
|
-
// 플레이스홀더
|
|
74
|
-
placeholderData: [], // 로딩 중 임시 데이터
|
|
33
|
+
staleTime: 1000 * 60 * 5, // fresh 유지 시간
|
|
34
|
+
gcTime: 1000 * 60 * 30, // 가비지 컬렉션 시간
|
|
35
|
+
refetchOnWindowFocus: true, // 포커스 시 리페치
|
|
36
|
+
refetchInterval: 1000 * 60, // 자동 리페치 간격
|
|
37
|
+
retry: 3, // 재시도 횟수
|
|
38
|
+
enabled: !!userId, // 조건부 실행
|
|
39
|
+
initialData: [], // 초기 데이터
|
|
40
|
+
select: (data) => data.filter(t => t.done), // 데이터 변환
|
|
75
41
|
})
|
|
76
42
|
```
|
|
77
43
|
|
|
78
|
-
##
|
|
44
|
+
## 패턴
|
|
79
45
|
|
|
80
46
|
```tsx
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return <div>{data?.title}</div>
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## 의존적 쿼리
|
|
93
|
-
|
|
94
|
-
```tsx
|
|
95
|
-
function UserPosts({ userId }: { userId: number }) {
|
|
96
|
-
// 먼저 유저 정보를 가져옴
|
|
97
|
-
const { data: user } = useQuery({
|
|
98
|
-
queryKey: ['user', userId],
|
|
99
|
-
queryFn: () => fetchUserById(userId),
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
// 유저 정보가 있어야 포스트를 가져옴
|
|
103
|
-
const { data: posts } = useQuery({
|
|
104
|
-
queryKey: ['posts', user?.id],
|
|
105
|
-
queryFn: () => fetchPostsByUserId(user!.id),
|
|
106
|
-
enabled: !!user?.id,
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<div>
|
|
111
|
-
<h1>{user?.name}</h1>
|
|
112
|
-
<ul>
|
|
113
|
-
{posts?.map((post) => (
|
|
114
|
-
<li key={post.id}>{post.title}</li>
|
|
115
|
-
))}
|
|
116
|
-
</ul>
|
|
117
|
-
</div>
|
|
118
|
-
)
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
## 병렬 쿼리
|
|
123
|
-
|
|
124
|
-
```tsx
|
|
125
|
-
function Dashboard() {
|
|
126
|
-
const usersQuery = useQuery({ queryKey: ['users'], queryFn: fetchUsers })
|
|
127
|
-
const postsQuery = useQuery({ queryKey: ['posts'], queryFn: fetchPosts })
|
|
128
|
-
|
|
129
|
-
if (usersQuery.isLoading || postsQuery.isLoading) {
|
|
130
|
-
return <div>Loading...</div>
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return (
|
|
134
|
-
<div>
|
|
135
|
-
<UserList users={usersQuery.data} />
|
|
136
|
-
<PostList posts={postsQuery.data} />
|
|
137
|
-
</div>
|
|
138
|
-
)
|
|
139
|
-
}
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
## useQueries (동적 병렬 쿼리)
|
|
143
|
-
|
|
144
|
-
```tsx
|
|
145
|
-
function UsersList({ userIds }: { userIds: number[] }) {
|
|
146
|
-
const userQueries = useQueries({
|
|
147
|
-
queries: userIds.map((id) => ({
|
|
148
|
-
queryKey: ['user', id],
|
|
149
|
-
queryFn: () => fetchUserById(id),
|
|
150
|
-
})),
|
|
151
|
-
})
|
|
47
|
+
// 파라미터 쿼리
|
|
48
|
+
useQuery({
|
|
49
|
+
queryKey: ['todo', todoId],
|
|
50
|
+
queryFn: () => fetchTodoById(todoId),
|
|
51
|
+
enabled: !!todoId,
|
|
52
|
+
})
|
|
152
53
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
</ul>
|
|
161
|
-
)
|
|
162
|
-
}
|
|
163
|
-
```
|
|
54
|
+
// 의존적 쿼리
|
|
55
|
+
const { data: user } = useQuery({ queryKey: ['user', userId], queryFn: ... })
|
|
56
|
+
const { data: posts } = useQuery({
|
|
57
|
+
queryKey: ['posts', user?.id],
|
|
58
|
+
queryFn: () => fetchPostsByUserId(user!.id),
|
|
59
|
+
enabled: !!user?.id, // user 로드 후 실행
|
|
60
|
+
})
|
|
164
61
|
|
|
165
|
-
|
|
62
|
+
// 병렬 쿼리
|
|
63
|
+
const usersQuery = useQuery({ queryKey: ['users'], queryFn: fetchUsers })
|
|
64
|
+
const postsQuery = useQuery({ queryKey: ['posts'], queryFn: fetchPosts })
|
|
166
65
|
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
66
|
+
// 동적 병렬 쿼리
|
|
67
|
+
const userQueries = useQueries({
|
|
68
|
+
queries: userIds.map((id) => ({
|
|
69
|
+
queryKey: ['user', id],
|
|
70
|
+
queryFn: () => fetchUserById(id),
|
|
71
|
+
})),
|
|
172
72
|
})
|
|
173
73
|
```
|
|
@@ -1,171 +1,78 @@
|
|
|
1
1
|
# TanStack Start - 인증 패턴
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
TanStack Start에서의 인증 구현 패턴입니다.
|
|
6
|
-
|
|
7
|
-
## 로그인
|
|
3
|
+
## 기본 인증 함수
|
|
8
4
|
|
|
9
5
|
```typescript
|
|
10
|
-
|
|
11
|
-
import { redirect } from '@tanstack/react-router'
|
|
12
|
-
import bcrypt from 'bcryptjs'
|
|
13
|
-
|
|
6
|
+
// 로그인
|
|
14
7
|
export const loginFn = createServerFn({ method: 'POST' })
|
|
15
8
|
.inputValidator((data: { email: string; password: string }) => data)
|
|
16
9
|
.handler(async ({ data }) => {
|
|
17
10
|
const user = await authenticateUser(data.email, data.password)
|
|
18
|
-
|
|
19
|
-
if (!user) {
|
|
20
|
-
return { error: 'Invalid credentials' }
|
|
21
|
-
}
|
|
11
|
+
if (!user) return { error: 'Invalid credentials' }
|
|
22
12
|
|
|
23
13
|
const session = await useAppSession()
|
|
24
|
-
await session.update({
|
|
25
|
-
userId: user.id,
|
|
26
|
-
email: user.email,
|
|
27
|
-
})
|
|
28
|
-
|
|
14
|
+
await session.update({ userId: user.id, email: user.email })
|
|
29
15
|
throw redirect({ to: '/dashboard' })
|
|
30
16
|
})
|
|
31
|
-
```
|
|
32
17
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
```typescript
|
|
18
|
+
// 로그아웃
|
|
36
19
|
export const logoutFn = createServerFn({ method: 'POST' })
|
|
37
20
|
.handler(async () => {
|
|
38
21
|
const session = await useAppSession()
|
|
39
22
|
await session.clear()
|
|
40
23
|
throw redirect({ to: '/' })
|
|
41
24
|
})
|
|
42
|
-
```
|
|
43
25
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
```typescript
|
|
26
|
+
// 현재 사용자
|
|
47
27
|
export const getCurrentUserFn = createServerFn({ method: 'GET' })
|
|
48
28
|
.handler(async () => {
|
|
49
29
|
const session = await useAppSession()
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (!userId) {
|
|
53
|
-
return null
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return await getUserById(userId)
|
|
57
|
-
})
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## 회원가입
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
export const registerFn = createServerFn({ method: 'POST' })
|
|
64
|
-
.inputValidator((data: { email: string; password: string; name: string }) => data)
|
|
65
|
-
.handler(async ({ data }) => {
|
|
66
|
-
const existingUser = await getUserByEmail(data.email)
|
|
67
|
-
if (existingUser) {
|
|
68
|
-
return { error: 'User already exists' }
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const hashedPassword = await bcrypt.hash(data.password, 12)
|
|
72
|
-
|
|
73
|
-
const user = await createUser({
|
|
74
|
-
email: data.email,
|
|
75
|
-
password: hashedPassword,
|
|
76
|
-
name: data.name,
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
const session = await useAppSession()
|
|
80
|
-
await session.update({ userId: user.id })
|
|
81
|
-
|
|
82
|
-
return { success: true, user: { id: user.id, email: user.email } }
|
|
30
|
+
if (!session.data.userId) return null
|
|
31
|
+
return getUserById(session.data.userId)
|
|
83
32
|
})
|
|
84
33
|
```
|
|
85
34
|
|
|
86
35
|
## 인증 미들웨어
|
|
87
36
|
|
|
88
37
|
```typescript
|
|
89
|
-
import { createMiddleware } from '@tanstack/react-start'
|
|
90
|
-
import { redirect } from '@tanstack/react-router'
|
|
91
|
-
|
|
92
38
|
export const authMiddleware = createMiddleware({ type: 'function' })
|
|
93
39
|
.server(async ({ next }) => {
|
|
94
40
|
const session = await useAppSession()
|
|
95
|
-
|
|
96
|
-
if (!session.data.userId) {
|
|
97
|
-
throw redirect({ to: '/login' })
|
|
98
|
-
}
|
|
99
|
-
|
|
41
|
+
if (!session.data.userId) throw redirect({ to: '/login' })
|
|
100
42
|
const user = await getUserById(session.data.userId)
|
|
101
|
-
|
|
102
43
|
return next({ context: { user } })
|
|
103
44
|
})
|
|
104
|
-
```
|
|
105
45
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
export const getProtectedData = createServerFn({ method: 'GET' })
|
|
46
|
+
// 사용
|
|
47
|
+
export const protectedFn = createServerFn({ method: 'GET' })
|
|
110
48
|
.middleware([authMiddleware])
|
|
111
|
-
.handler(async ({ context }) => {
|
|
112
|
-
// context.user 사용 가능
|
|
113
|
-
return {
|
|
114
|
-
message: `Hello, ${context.user.name}!`,
|
|
115
|
-
data: await getPrivateData(context.user.id),
|
|
116
|
-
}
|
|
117
|
-
})
|
|
49
|
+
.handler(async ({ context }) => ({ user: context.user }))
|
|
118
50
|
```
|
|
119
51
|
|
|
120
52
|
## 보호된 라우트
|
|
121
53
|
|
|
122
54
|
```tsx
|
|
123
|
-
// routes/dashboard.tsx
|
|
124
|
-
import { createFileRoute, redirect } from '@tanstack/react-router'
|
|
125
|
-
|
|
126
55
|
export const Route = createFileRoute('/dashboard')({
|
|
127
56
|
beforeLoad: async () => {
|
|
128
57
|
const user = await getCurrentUserFn()
|
|
129
|
-
|
|
130
|
-
if (!user) {
|
|
131
|
-
throw redirect({ to: '/login' })
|
|
132
|
-
}
|
|
133
|
-
|
|
58
|
+
if (!user) throw redirect({ to: '/login' })
|
|
134
59
|
return { user }
|
|
135
60
|
},
|
|
136
|
-
component:
|
|
61
|
+
component: () => {
|
|
62
|
+
const { user } = Route.useRouteContext()
|
|
63
|
+
return <h1>Welcome, {user.name}!</h1>
|
|
64
|
+
},
|
|
137
65
|
})
|
|
138
|
-
|
|
139
|
-
function DashboardPage() {
|
|
140
|
-
const { user } = Route.useRouteContext()
|
|
141
|
-
return <h1>Welcome, {user.name}!</h1>
|
|
142
|
-
}
|
|
143
66
|
```
|
|
144
67
|
|
|
145
68
|
## Better Auth 통합
|
|
146
69
|
|
|
147
70
|
```typescript
|
|
148
|
-
// lib/auth.ts
|
|
149
|
-
import { betterAuth } from 'better-auth'
|
|
150
|
-
import { prismaAdapter } from 'better-auth/adapters/prisma'
|
|
151
|
-
import { prisma } from './prisma'
|
|
152
|
-
|
|
153
71
|
export const auth = betterAuth({
|
|
154
72
|
database: prismaAdapter(prisma),
|
|
155
|
-
emailAndPassword: {
|
|
156
|
-
enabled: true,
|
|
157
|
-
},
|
|
73
|
+
emailAndPassword: { enabled: true },
|
|
158
74
|
})
|
|
159
75
|
|
|
160
|
-
// Server Function에서 사용
|
|
161
|
-
import { createServerFn } from '@tanstack/react-start'
|
|
162
|
-
import { auth } from '@/lib/auth'
|
|
163
|
-
|
|
164
76
|
export const getSession = createServerFn({ method: 'GET' })
|
|
165
|
-
.handler(async ({ request }) => {
|
|
166
|
-
const session = await auth.api.getSession({
|
|
167
|
-
headers: request.headers,
|
|
168
|
-
})
|
|
169
|
-
return session
|
|
170
|
-
})
|
|
77
|
+
.handler(async ({ request }) => auth.api.getSession({ headers: request.headers }))
|
|
171
78
|
```
|
|
@@ -1,33 +1,40 @@
|
|
|
1
1
|
# TanStack Start
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> 1.x | Full-stack React Framework
|
|
4
|
+
|
|
5
|
+
@setup.md
|
|
6
|
+
@server-functions.md
|
|
7
|
+
@middleware.md
|
|
8
|
+
@routing.md
|
|
9
|
+
@auth-patterns.md
|
|
4
10
|
|
|
5
11
|
---
|
|
6
12
|
|
|
7
13
|
## ⛔ 필수 규칙
|
|
8
14
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
| 금지 | 대신 |
|
|
16
|
+
|------|------|
|
|
17
|
+
| /api 라우터 | Server Functions |
|
|
18
|
+
| handler 내 수동 검증 | inputValidator |
|
|
19
|
+
| handler 내 수동 인증 | middleware |
|
|
20
|
+
|
|
21
|
+
✅ POST/PUT/PATCH → inputValidator 필수
|
|
22
|
+
✅ 인증 필요 → middleware 필수
|
|
16
23
|
|
|
17
24
|
---
|
|
18
25
|
|
|
19
|
-
##
|
|
26
|
+
## Quick Reference
|
|
20
27
|
|
|
21
28
|
```typescript
|
|
22
|
-
//
|
|
29
|
+
// GET + 인증
|
|
23
30
|
export const getUsers = createServerFn({ method: 'GET' })
|
|
24
|
-
.middleware([authMiddleware])
|
|
31
|
+
.middleware([authMiddleware])
|
|
25
32
|
.handler(async () => prisma.user.findMany())
|
|
26
33
|
|
|
27
|
-
//
|
|
34
|
+
// POST + Validation + 인증
|
|
28
35
|
export const createUser = createServerFn({ method: 'POST' })
|
|
29
|
-
.middleware([authMiddleware])
|
|
30
|
-
.inputValidator(createUserSchema)
|
|
36
|
+
.middleware([authMiddleware])
|
|
37
|
+
.inputValidator(createUserSchema)
|
|
31
38
|
.handler(async ({ data }) => prisma.user.create({ data }))
|
|
32
39
|
|
|
33
40
|
// Route with Loader
|
|
@@ -36,79 +43,25 @@ export const Route = createFileRoute('/users')({
|
|
|
36
43
|
loader: async () => ({ users: await getUsers() }),
|
|
37
44
|
})
|
|
38
45
|
|
|
39
|
-
//
|
|
46
|
+
// Loader 데이터 사용
|
|
40
47
|
const UsersPage = (): JSX.Element => {
|
|
41
48
|
const { users } = Route.useLoaderData()
|
|
42
49
|
return <div>{/* render */}</div>
|
|
43
50
|
}
|
|
44
51
|
```
|
|
45
52
|
|
|
46
|
-
###
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
routes/
|
|
50
|
-
├── __root.tsx # Root layout
|
|
51
|
-
├── index.tsx # /
|
|
52
|
-
├── about.tsx # /about
|
|
53
|
-
└── users/
|
|
54
|
-
├── index.tsx # /users
|
|
55
|
-
├── $id.tsx # /users/:id
|
|
56
|
-
├── -components/ # 페이지 전용 컴포넌트
|
|
57
|
-
└── -functions/ # 페이지 전용 Server Functions ⭐
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Server Functions 위치
|
|
61
|
-
|
|
62
|
-
```
|
|
63
|
-
공통 함수 (여러 라우트) → @/functions/
|
|
64
|
-
라우트 전용 함수 → routes/[경로]/-functions/
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
## 문서 구조
|
|
70
|
-
|
|
71
|
-
- [설치 및 설정](./setup.md) - 패키지 설치, Vite 설정, TypeScript 설정
|
|
72
|
-
- [Server Functions](./server-functions.md) - 서버 함수 정의 및 사용법
|
|
73
|
-
- [Middleware](./middleware.md) - 미들웨어 정의 및 적용
|
|
74
|
-
- [Routing](./routing.md) - 파일 기반 라우팅, 동적 라우트, SSR
|
|
75
|
-
- [인증 패턴](./auth-patterns.md) - 로그인, 로그아웃, 세션 관리
|
|
76
|
-
|
|
77
|
-
## 빠른 시작
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
yarn add @tanstack/react-start @tanstack/react-router vinxi
|
|
81
|
-
yarn add -D vite @vitejs/plugin-react vite-tsconfig-paths
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## 핵심 개념
|
|
85
|
-
|
|
86
|
-
### Server Functions
|
|
87
|
-
서버에서만 실행되는 타입 안전한 함수를 정의합니다.
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
import { createServerFn } from '@tanstack/react-start'
|
|
91
|
-
|
|
92
|
-
export const getData = createServerFn({ method: 'GET' })
|
|
93
|
-
.handler(async () => {
|
|
94
|
-
return { message: 'Hello from server' }
|
|
95
|
-
})
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### File-based Routing
|
|
99
|
-
파일 시스템 기반의 자동 라우팅을 지원합니다.
|
|
53
|
+
### 구조
|
|
100
54
|
|
|
101
55
|
```
|
|
102
56
|
routes/
|
|
103
|
-
├──
|
|
104
|
-
├──
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
57
|
+
├── __root.tsx # Root layout
|
|
58
|
+
├── index.tsx # /
|
|
59
|
+
├── users/
|
|
60
|
+
│ ├── index.tsx # /users
|
|
61
|
+
│ ├── $id.tsx # /users/:id
|
|
62
|
+
│ ├── -components/ # 페이지 전용
|
|
63
|
+
│ └── -functions/ # 페이지 전용 Server Functions
|
|
64
|
+
|
|
65
|
+
공통 함수 → @/functions/
|
|
66
|
+
라우트 전용 → routes/[경로]/-functions/
|
|
108
67
|
```
|
|
109
|
-
|
|
110
|
-
## 참고 자료
|
|
111
|
-
|
|
112
|
-
- [TanStack Start 공식 문서](https://tanstack.com/start/latest)
|
|
113
|
-
- [TanStack Router 문서](https://tanstack.com/router/latest)
|
|
114
|
-
- [Vinxi 문서](https://vinxi.dev)
|