@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.
Files changed (75) hide show
  1. package/dist/index.js +21 -243
  2. package/package.json +1 -1
  3. package/templates/hono/CLAUDE.md +10 -6
  4. package/templates/hono/docs/deployment/index.md +5 -0
  5. package/templates/hono/docs/library/hono/index.md +6 -0
  6. package/templates/hono/docs/library/prisma/index.md +3 -0
  7. package/templates/npx/CLAUDE.md +8 -2
  8. package/templates/tanstack-start/CLAUDE.md +103 -255
  9. package/templates/tanstack-start/docs/deployment/cloudflare.md +37 -424
  10. package/templates/tanstack-start/docs/deployment/index.md +57 -286
  11. package/templates/tanstack-start/docs/deployment/nitro.md +36 -318
  12. package/templates/tanstack-start/docs/deployment/railway.md +40 -409
  13. package/templates/tanstack-start/docs/deployment/vercel.md +43 -465
  14. package/templates/tanstack-start/docs/design/accessibility.md +56 -326
  15. package/templates/tanstack-start/docs/design/color.md +37 -179
  16. package/templates/tanstack-start/docs/design/components.md +77 -311
  17. package/templates/tanstack-start/docs/design/index.md +24 -87
  18. package/templates/tanstack-start/docs/design/safe-area.md +51 -250
  19. package/templates/tanstack-start/docs/design/spacing.md +57 -276
  20. package/templates/tanstack-start/docs/design/tailwind-setup.md +45 -359
  21. package/templates/tanstack-start/docs/design/typography.md +40 -284
  22. package/templates/tanstack-start/docs/library/better-auth/2fa.md +27 -115
  23. package/templates/tanstack-start/docs/library/better-auth/advanced.md +22 -105
  24. package/templates/tanstack-start/docs/library/better-auth/index.md +17 -66
  25. package/templates/tanstack-start/docs/library/better-auth/plugins.md +11 -88
  26. package/templates/tanstack-start/docs/library/better-auth/session.md +12 -92
  27. package/templates/tanstack-start/docs/library/better-auth/setup.md +9 -91
  28. package/templates/tanstack-start/docs/library/prisma/cloudflare-d1.md +30 -358
  29. package/templates/tanstack-start/docs/library/prisma/config.md +27 -327
  30. package/templates/tanstack-start/docs/library/prisma/crud.md +46 -174
  31. package/templates/tanstack-start/docs/library/prisma/index.md +23 -113
  32. package/templates/tanstack-start/docs/library/prisma/relations.md +31 -153
  33. package/templates/tanstack-start/docs/library/prisma/schema.md +40 -217
  34. package/templates/tanstack-start/docs/library/prisma/setup.md +12 -112
  35. package/templates/tanstack-start/docs/library/prisma/transactions.md +20 -110
  36. package/templates/tanstack-start/docs/library/tanstack-query/index.md +12 -99
  37. package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +28 -107
  38. package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +44 -146
  39. package/templates/tanstack-start/docs/library/tanstack-query/setup.md +11 -70
  40. package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +33 -127
  41. package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +49 -149
  42. package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +19 -112
  43. package/templates/tanstack-start/docs/library/tanstack-start/index.md +33 -80
  44. package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +28 -106
  45. package/templates/tanstack-start/docs/library/tanstack-start/routing.md +21 -118
  46. package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +34 -246
  47. package/templates/tanstack-start/docs/library/tanstack-start/setup.md +6 -39
  48. package/templates/tanstack-start/docs/library/zod/basic-types.md +33 -145
  49. package/templates/tanstack-start/docs/library/zod/complex-types.md +32 -156
  50. package/templates/tanstack-start/docs/library/zod/index.md +22 -150
  51. package/templates/tanstack-start/docs/library/zod/transforms.md +20 -129
  52. package/templates/tanstack-start/docs/library/zod/validation.md +39 -155
  53. package/templates/hono/docs/commands/git.md +0 -145
  54. package/templates/hono/docs/mcp/context7.md +0 -106
  55. package/templates/hono/docs/mcp/index.md +0 -176
  56. package/templates/hono/docs/mcp/sequential-thinking.md +0 -101
  57. package/templates/hono/docs/mcp/serena.md +0 -269
  58. package/templates/hono/docs/mcp/sgrep.md +0 -105
  59. package/templates/hono/docs/skills/gemini-review/SKILL.md +0 -220
  60. package/templates/hono/docs/skills/gemini-review/references/checklists.md +0 -136
  61. package/templates/hono/docs/skills/gemini-review/references/prompt-templates.md +0 -303
  62. package/templates/npx/docs/commands/git.md +0 -145
  63. package/templates/npx/docs/mcp/index.md +0 -60
  64. package/templates/npx/docs/skills/gemini-review/SKILL.md +0 -220
  65. package/templates/npx/docs/skills/gemini-review/references/checklists.md +0 -134
  66. package/templates/npx/docs/skills/gemini-review/references/prompt-templates.md +0 -301
  67. package/templates/tanstack-start/docs/commands/git.md +0 -145
  68. package/templates/tanstack-start/docs/mcp/context7.md +0 -204
  69. package/templates/tanstack-start/docs/mcp/index.md +0 -177
  70. package/templates/tanstack-start/docs/mcp/sequential-thinking.md +0 -180
  71. package/templates/tanstack-start/docs/mcp/serena.md +0 -269
  72. package/templates/tanstack-start/docs/mcp/sgrep.md +0 -174
  73. package/templates/tanstack-start/docs/skills/gemini-review/SKILL.md +0 -220
  74. package/templates/tanstack-start/docs/skills/gemini-review/references/checklists.md +0 -144
  75. package/templates/tanstack-start/docs/skills/gemini-review/references/prompt-templates.md +0 -292
@@ -1,142 +1,64 @@
1
1
  # TanStack Start - Middleware
2
2
 
3
- > **상위 문서**: [TanStack Start](./index.md)
3
+ Server Function 라우트에 공통 로직 적용.
4
4
 
5
- 미들웨어는 Server Function 및 라우트 핸들러에 공통 로직을 적용합니다.
6
-
7
- ## Server Function Middleware
5
+ ## 기본 패턴
8
6
 
9
7
  ```typescript
10
- import { createMiddleware, createServerFn } from '@tanstack/react-start'
11
-
12
8
  // 미들웨어 정의
13
9
  const loggingMiddleware = createMiddleware({ type: 'function' })
14
- .client(() => {
15
- console.log('Client: Server function called')
16
- })
17
10
  .server(({ next }) => {
18
- console.log('Server: Processing request')
11
+ console.log('Processing request')
19
12
  return next()
20
13
  })
21
14
 
22
- // 미들웨어 적용
15
+ // 적용
23
16
  const fn = createServerFn()
24
17
  .middleware([loggingMiddleware])
25
- .handler(async () => {
26
- return { message: 'Hello' }
27
- })
18
+ .handler(async () => ({ message: 'Hello' }))
28
19
  ```
29
20
 
30
- ## Zod Validation Middleware
21
+ ## 인증 미들웨어
31
22
 
32
23
  ```typescript
33
- import { createMiddleware } from '@tanstack/react-start'
34
- import { zodValidator } from '@tanstack/react-start/validators'
35
- import { z } from 'zod'
36
-
37
- const mySchema = z.object({
38
- workspaceId: z.string(),
39
- })
40
-
41
- const workspaceMiddleware = createMiddleware({ type: 'function' })
42
- .inputValidator(zodValidator(mySchema))
43
- .server(({ next, data }) => {
44
- console.log('Workspace ID:', data.workspaceId)
45
- return next()
24
+ const authMiddleware = createMiddleware({ type: 'function' })
25
+ .server(async ({ next }) => {
26
+ const session = await getSession()
27
+ if (!session) throw redirect({ to: '/login' })
28
+ return next({ context: { user: session.user } })
46
29
  })
47
- ```
48
30
 
49
- ## Global Request Middleware
50
-
51
- ```typescript
52
- // src/start.ts
53
- import { createStart, createMiddleware } from '@tanstack/react-start'
54
-
55
- const myGlobalMiddleware = createMiddleware().server(({ next }) => {
56
- console.log('Global middleware running')
57
- return next()
58
- })
59
-
60
- export const startInstance = createStart(() => {
61
- return {
62
- requestMiddleware: [myGlobalMiddleware],
63
- }
64
- })
31
+ // 사용
32
+ export const protectedFn = createServerFn({ method: 'GET' })
33
+ .middleware([authMiddleware])
34
+ .handler(async ({ context }) => ({ user: context.user }))
65
35
  ```
66
36
 
67
- ## Global Server Function Middleware
37
+ ## Zod Validation Middleware
68
38
 
69
39
  ```typescript
70
- // src/start.ts
71
- import { createStart } from '@tanstack/react-start'
72
- import { loggingMiddleware } from './middleware'
73
-
74
- export const startInstance = createStart(() => {
75
- return {
76
- functionMiddleware: [loggingMiddleware],
77
- }
78
- })
40
+ const workspaceMiddleware = createMiddleware({ type: 'function' })
41
+ .inputValidator(zodValidator(z.object({ workspaceId: z.string() })))
42
+ .server(({ next, data }) => next())
79
43
  ```
80
44
 
81
- ## Route-Level Middleware
45
+ ## Global Middleware
82
46
 
83
47
  ```typescript
84
- export const Route = createFileRoute('/hello')({
85
- server: {
86
- middleware: [authMiddleware, loggerMiddleware], // 모든 핸들러에 적용
87
- handlers: {
88
- GET: async ({ request }) => {
89
- return new Response('Hello, World! from ' + request.url)
90
- },
91
- POST: async ({ request }) => {
92
- const body = await request.json()
93
- return new Response(`Hello, ${body.name}!`)
94
- },
95
- },
96
- },
97
- })
48
+ // src/start.ts
49
+ export const startInstance = createStart(() => ({
50
+ requestMiddleware: [globalMiddleware], // 모든 요청
51
+ functionMiddleware: [loggingMiddleware], // 모든 Server Function
52
+ }))
98
53
  ```
99
54
 
100
- ## Handler-Specific Middleware
55
+ ## Route-Level
101
56
 
102
57
  ```typescript
103
58
  export const Route = createFileRoute('/hello')({
104
59
  server: {
105
- handlers: ({ createHandlers }) =>
106
- createHandlers({
107
- GET: {
108
- middleware: [loggerMiddleware], // GET에만 적용
109
- handler: async ({ request }) => {
110
- return new Response('Hello, World! from ' + request.url)
111
- },
112
- },
113
- }),
60
+ middleware: [authMiddleware], // 모든 핸들러
61
+ handlers: { GET: async ({ request }) => new Response('Hello') },
114
62
  },
115
63
  })
116
64
  ```
117
-
118
- ## 인증 미들웨어 예시
119
-
120
- ```typescript
121
- import { createMiddleware } from '@tanstack/react-start'
122
- import { redirect } from '@tanstack/react-router'
123
-
124
- const authMiddleware = createMiddleware({ type: 'function' })
125
- .server(async ({ next }) => {
126
- const session = await getSession()
127
-
128
- if (!session) {
129
- throw redirect({ to: '/login' })
130
- }
131
-
132
- return next({ context: { user: session.user } })
133
- })
134
-
135
- // 사용
136
- export const protectedFn = createServerFn({ method: 'GET' })
137
- .middleware([authMiddleware])
138
- .handler(async ({ context }) => {
139
- // context.user 사용 가능
140
- return { user: context.user }
141
- })
142
- ```
@@ -1,163 +1,66 @@
1
1
  # TanStack Start - Routing
2
2
 
3
- > **상위 문서**: [TanStack Start](./index.md)
3
+ 파일 기반 라우팅.
4
4
 
5
- TanStack Start는 파일 기반 라우팅을 지원합니다.
6
-
7
- ## 기본 라우트
5
+ ## 기본 패턴
8
6
 
9
7
  ```tsx
10
8
  // routes/about.tsx
11
- import { createFileRoute } from '@tanstack/react-router'
12
-
13
9
  export const Route = createFileRoute('/about')({
14
10
  component: AboutPage,
15
11
  })
16
12
 
17
- function AboutPage() {
18
- return <h1>About</h1>
19
- }
20
- ```
21
-
22
- ## Loader를 사용한 데이터 로딩
23
-
24
- ```tsx
25
- // routes/index.tsx
26
- import { createFileRoute } from '@tanstack/react-router'
27
-
13
+ // Loader
28
14
  export const Route = createFileRoute('/')({
29
15
  component: Page,
30
- loader: async () => {
31
- const res = await fetch('https://api.example.com/posts')
32
- return res.json()
33
- },
16
+ loader: async () => fetch('/api/posts').then(r => r.json()),
34
17
  })
35
18
 
36
19
  function Page() {
37
20
  const posts = Route.useLoaderData()
38
- return (
39
- <ul>
40
- {posts.map((post) => (
41
- <li key={post.id}>{post.title}</li>
42
- ))}
43
- </ul>
44
- )
21
+ return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
45
22
  }
46
- ```
47
-
48
- ## 동적 라우트
49
-
50
- ```tsx
51
- // routes/users/$id.tsx
52
- import { createFileRoute } from '@tanstack/react-router'
53
23
 
24
+ // 동적 라우트
54
25
  export const Route = createFileRoute('/users/$id')({
55
- loader: async ({ params }) => {
56
- const user = await getUserById(params.id)
57
- return { user }
58
- },
59
- component: UserDetailPage,
60
- })
61
-
62
- function UserDetailPage() {
63
- const { user } = Route.useLoaderData()
64
- return <h1>{user.name}</h1>
65
- }
66
- ```
67
-
68
- ## SSR 설정
69
-
70
- ```tsx
71
- // routes/posts/$postId.tsx
72
- export const Route = createFileRoute('/posts/$postId')({
73
- ssr: true, // SSR 활성화
74
- beforeLoad: () => {
75
- console.log('서버에서 초기 요청 시 실행')
76
- },
77
- loader: () => {
78
- console.log('서버에서 초기 요청 시 실행')
26
+ loader: async ({ params }) => ({ user: await getUserById(params.id) }),
27
+ component: () => {
28
+ const { user } = Route.useLoaderData()
29
+ return <h1>{user.name}</h1>
79
30
  },
80
- component: () => <div>서버에서 렌더링됨</div>,
81
31
  })
82
32
  ```
83
33
 
84
- ### SSR 옵션
34
+ ## SSR 옵션
85
35
 
86
36
  ```typescript
87
- // ssr: true - 전체 SSR (기본값)
88
- // ssr: false - 클라이언트 사이드만
89
- // ssr: 'data-only' - 데이터만 서버에서 로드, 렌더링은 클라이언트
37
+ ssr: true // 전체 SSR (기본값)
38
+ ssr: false // 클라이언트만
39
+ ssr: 'data-only' // 데이터만 서버, 렌더링은 클라이언트
90
40
  ```
91
41
 
92
42
  ## Server Routes (API)
93
43
 
94
- ### 기본 API 라우트
95
-
96
44
  ```typescript
97
- // routes/api/hello.ts
98
- import { createFileRoute } from '@tanstack/react-router'
99
-
100
45
  export const Route = createFileRoute('/api/hello')({
101
46
  server: {
102
47
  handlers: {
103
- GET: async ({ request }) => {
104
- return new Response('Hello, World!')
105
- },
48
+ GET: async () => new Response('Hello'),
106
49
  POST: async ({ request }) => {
107
50
  const body = await request.json()
108
- return new Response(`Hello, ${body.name}!`)
51
+ return json({ name: body.name })
109
52
  },
110
53
  },
111
54
  },
112
55
  })
113
56
  ```
114
57
 
115
- ### JSON 응답
116
-
117
- ```typescript
118
- import { createFileRoute } from '@tanstack/react-router'
119
- import { json } from '@tanstack/react-start'
120
-
121
- export const Route = createFileRoute('/api/users')({
122
- server: {
123
- handlers: {
124
- GET: async ({ request }) => {
125
- const users = await getUsers()
126
- return json({ users })
127
- },
128
- },
129
- },
130
- })
131
- ```
132
-
133
- ## 라우트 파일 구조
58
+ ## 구조
134
59
 
135
60
  ```
136
61
  routes/
137
- ├── __root.tsx → Root layout
138
- ├── index.tsx → /
139
- ├── about.tsx → /about
140
- ├── users/
141
- │ ├── index.tsx → /users
142
- │ └── $id.tsx → /users/:id
143
- ├── posts/
144
- │ ├── index.tsx → /posts
145
- │ ├── $postId.tsx → /posts/:postId
146
- │ └── new.tsx → /posts/new
147
- └── api/
148
- ├── hello.ts → /api/hello
149
- └── users.ts → /api/users
150
- ```
151
-
152
- ## Catch-All 라우트
153
-
154
- ```tsx
155
- // routes/$.tsx - 모든 매칭되지 않는 경로 처리
156
- export const Route = createFileRoute('/$')({
157
- component: NotFoundPage,
158
- })
159
-
160
- function NotFoundPage() {
161
- return <h1>404 - Page Not Found</h1>
162
- }
62
+ ├── __root.tsx → Root layout
63
+ ├── index.tsx → /
64
+ ├── users/$id.tsx → /users/:id
65
+ ├── $.tsx → Catch-all (404)
163
66
  ```
@@ -1,287 +1,75 @@
1
1
  # TanStack Start - Server Functions
2
2
 
3
- > **상위 문서**: [TanStack Start](./index.md)
4
-
5
- Server Functions는 서버에서만 실행되는 타입 안전한 함수입니다.
3
+ 서버에서만 실행되는 타입 안전한 함수.
6
4
 
7
5
  ## ⚠️ 필수: TanStack Query 사용
8
6
 
9
- **Server Function을 클라이언트에서 호출할 때는 반드시 TanStack Query를 사용해야 합니다.**
10
-
11
- ```
12
- ❌ 금지: Server Function 직접 호출
13
- ✅ 필수: useQuery/useMutation과 함께 사용
14
- ```
15
-
16
- **이유**:
17
- - 자동 캐싱 및 중복 요청 제거
18
- - 로딩/에러 상태 관리
19
- - 자동 재시도 및 백그라운드 갱신
20
- - invalidateQueries로 일관된 데이터 동기화
7
+ 클라이언트 호출 반드시 useQuery/useMutation 사용.
8
+ - 자동 캐싱, 중복 요청 제거, 로딩/에러 상태 관리, invalidateQueries 동기화
21
9
 
22
- ## 기본 Server Function
10
+ ## 기본 패턴
23
11
 
24
12
  ```typescript
25
- import { createServerFn } from '@tanstack/react-start'
26
-
27
- // GET 요청 (데이터 조회)
13
+ // GET
28
14
  export const getUsers = createServerFn({ method: 'GET' })
29
- .handler(async () => {
30
- return prisma.user.findMany()
31
- })
32
-
33
- // POST 요청 (데이터 생성/수정)
34
- export const createUser = createServerFn({ method: 'POST' })
35
- .handler(async () => {
36
- return { success: true }
37
- })
38
- ```
39
-
40
- ## Input Validation
41
-
42
- ### 기본 Validator
43
-
44
- ```typescript
45
- import { createServerFn } from '@tanstack/react-start'
46
-
47
- export const createUser = createServerFn({ method: 'POST' })
48
- .inputValidator((data: { email: string; name: string }) => data)
49
- .handler(async ({ data }) => {
50
- // data는 타입 안전함
51
- return prisma.user.create({ data })
52
- })
53
- ```
54
-
55
- ### Zod Validation 사용
56
-
57
- ```typescript
58
- import { createServerFn } from '@tanstack/react-start'
59
- import { zodValidator } from '@tanstack/react-start/validators'
60
- import { z } from 'zod'
15
+ .handler(async () => prisma.user.findMany())
61
16
 
17
+ // POST + Zod Validation
62
18
  const createUserSchema = z.object({
63
- email: z.string().email(),
19
+ email: z.email(),
64
20
  name: z.string().min(1).max(100),
65
- bio: z.string().max(500).optional(),
66
21
  })
67
22
 
68
23
  export const createUser = createServerFn({ method: 'POST' })
69
24
  .inputValidator(zodValidator(createUserSchema))
70
- .handler(async ({ data }) => {
71
- return prisma.user.create({ data })
72
- })
73
- ```
74
-
75
- ## FormData 처리
76
-
77
- ```typescript
78
- import { createServerFn } from '@tanstack/react-start'
79
-
80
- export const submitForm = createServerFn({ method: 'POST' })
81
- .inputValidator((formData: FormData) => {
82
- const name = formData.get('name') as string
83
- const email = formData.get('email') as string
84
- return { name, email }
85
- })
86
- .handler(async ({ data }) => {
87
- return prisma.user.create({ data })
88
- })
25
+ .handler(async ({ data }) => prisma.user.create({ data }))
89
26
  ```
90
27
 
91
- ## 컴포넌트에서 호출 (TanStack Query 필수)
92
-
93
- ### ✅ 올바른 패턴: useQuery 사용 (데이터 조회)
28
+ ## 컴포넌트에서 호출
94
29
 
95
30
  ```tsx
96
- import { useQuery } from '@tanstack/react-query'
97
- import { getServerPosts } from '@/lib/server-functions'
98
-
99
- function PostList() {
100
- const { data, isLoading, error } = useQuery({
101
- queryKey: ['posts'],
102
- queryFn: () => getServerPosts(),
103
- })
104
-
105
- if (isLoading) return <div>Loading...</div>
106
- if (error) return <div>Error: {error.message}</div>
107
-
108
- return (
109
- <ul>
110
- {data?.map((post) => (
111
- <li key={post.id}>{post.title}</li>
112
- ))}
113
- </ul>
114
- )
115
- }
116
- ```
117
-
118
- ### ✅ 올바른 패턴: useMutation 사용 (데이터 변경)
119
-
120
- ```tsx
121
- import { useMutation, useQueryClient } from '@tanstack/react-query'
122
- import { createPost, deletePost } from '@/lib/server-functions'
123
-
124
- function PostForm() {
125
- const queryClient = useQueryClient()
126
-
127
- const mutation = useMutation({
128
- mutationFn: (data: { title: string; content: string }) => createPost({ data }),
129
- onSuccess: () => {
130
- // 관련 쿼리 무효화로 데이터 동기화
131
- queryClient.invalidateQueries({ queryKey: ['posts'] })
132
- },
133
- })
134
-
135
- const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
136
- e.preventDefault()
137
- const formData = new FormData(e.currentTarget)
138
- mutation.mutate({
139
- title: formData.get('title') as string,
140
- content: formData.get('content') as string,
141
- })
142
- }
143
-
144
- return (
145
- <form onSubmit={handleSubmit}>
146
- <input name="title" required />
147
- <textarea name="content" required />
148
- <button type="submit" disabled={mutation.isPending}>
149
- {mutation.isPending ? '저장 중...' : '저장'}
150
- </button>
151
- </form>
152
- )
153
- }
154
- ```
155
-
156
- ### ❌ 금지: Server Function 직접 호출
157
-
158
- ```tsx
159
- // ❌ 이렇게 하지 마세요!
160
- function BadExample() {
161
- const [posts, setPosts] = useState([])
162
- const [loading, setLoading] = useState(false)
163
-
164
- useEffect(() => {
165
- setLoading(true)
166
- getPosts()
167
- .then(setPosts)
168
- .finally(() => setLoading(false))
169
- }, [])
170
-
171
- // 문제점:
172
- // - 중복 요청 발생 가능
173
- // - 캐싱 없음
174
- // - 에러 처리 수동
175
- // - 다른 컴포넌트와 데이터 동기화 안됨
176
- }
177
- ```
178
-
179
- ## ⚠️ 함수 분리 시 유의사항
180
-
181
- Server Function 내부 로직을 별도 함수로 분리할 때 반드시 아래 규칙을 따르세요.
31
+ // useQuery (조회)
32
+ const { data, isLoading } = useQuery({
33
+ queryKey: ['posts'],
34
+ queryFn: () => getServerPosts(),
35
+ })
182
36
 
183
- ### 규칙
37
+ // ✅ useMutation (변경)
38
+ const mutation = useMutation({
39
+ mutationFn: createPost,
40
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['posts'] }),
41
+ })
184
42
 
185
- ```
186
- 1. 분리한 함수는 createServerFn 내부에서만 호출
187
- 2. 분리한 함수는 createServerFn으로 감싸지 않음
188
- 3. 분리한 함수는 index.ts에서 export 금지 (프론트엔드 import 방지)
43
+ // ❌ 직접 호출 금지 (캐싱 없음, 동기화 안됨)
189
44
  ```
190
45
 
191
- ### 올바른 패턴
46
+ ## 함수 분리 규칙
192
47
 
193
48
  ```typescript
194
- // services/user/mutations.ts
195
-
196
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
197
- // 내부 헬퍼 함수 (export 금지!)
198
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
199
- const validateUserData = async (email: string) => {
200
- const existing = await prisma.user.findUnique({ where: { email } })
201
- if (existing) throw new Error('Email already exists')
202
- return true
203
- }
49
+ // 내부 헬퍼 (export 금지!)
50
+ const validateUserData = async (email: string) => { ... }
204
51
 
205
- const sendWelcomeEmail = async (userId: string, email: string) => {
206
- await emailService.send({
207
- to: email,
208
- template: 'welcome',
209
- data: { userId },
210
- })
211
- }
212
-
213
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
214
52
  // Server Function (export 가능)
215
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
216
53
  export const createUser = createServerFn({ method: 'POST' })
217
54
  .inputValidator(createUserSchema)
218
55
  .handler(async ({ data }) => {
219
- // 내부 헬퍼 함수 호출
220
56
  await validateUserData(data.email)
221
-
222
- const user = await prisma.user.create({ data })
223
-
224
- // 내부 헬퍼 함수 호출
225
- await sendWelcomeEmail(user.id, user.email)
226
-
227
- return user
228
- })
229
- ```
230
-
231
- ```typescript
232
- // services/user/index.ts
233
-
234
- // ✅ Server Function만 export
235
- export { createUser, updateUser, deleteUser } from './mutations'
236
- export { getUsers, getUserById } from './queries'
237
-
238
- // ❌ 내부 헬퍼 함수는 export 금지!
239
- // export { validateUserData, sendWelcomeEmail } from './mutations'
240
- ```
241
-
242
- ### ❌ 잘못된 패턴
243
-
244
- ```typescript
245
- // ❌ 분리한 함수를 createServerFn으로 감싸지 마세요
246
- export const validateUserData = createServerFn({ method: 'POST' })
247
- .handler(async ({ data }) => {
248
- // ...
57
+ return prisma.user.create({ data })
249
58
  })
250
59
 
251
- // 내부 헬퍼 함수를 export 하지 마세요
252
- export const sendWelcomeEmail = async (userId: string) => {
253
- // 프론트엔드에서 import 가능해져 보안 위험!
254
- }
60
+ // index.ts: Server Function만 export
61
+ export { createUser } from './mutations'
62
+ // export { validateUserData } 금지
255
63
  ```
256
64
 
257
- ### 이유
258
-
259
- - **보안**: 내부 로직이 프론트엔드 번들에 포함되지 않음
260
- - **명확한 API**: Server Function만 외부에 노출되어 API 경계가 명확함
261
- - **트리 쉐이킹**: export하지 않은 함수는 번들에서 제거됨
262
-
263
- ---
264
-
265
- ## 보안 패턴
266
-
267
- ### 서버 전용 데이터 보호
65
+ ## 보안
268
66
 
269
67
  ```tsx
270
- // ❌ 잘못된 방법 - 클라이언트에 노출됨
271
- export const Route = createFileRoute('/users')({
272
- loader: () => {
273
- const secret = process.env.SECRET // 클라이언트에 노출!
274
- return fetch(`/api/users?key=${secret}`)
275
- },
276
- })
277
-
278
- // ✅ 올바른 방법 - 서버 함수 사용
279
- const getUsersSecurely = createServerFn().handler(() => {
280
- const secret = process.env.SECRET // 서버에서만 접근
281
- return fetch(`/api/users?key=${secret}`)
282
- })
68
+ // ❌ loader에서 환경변수 직접 사용 (노출됨)
69
+ loader: () => { const secret = process.env.SECRET }
283
70
 
284
- export const Route = createFileRoute('/users')({
285
- loader: () => getUsersSecurely(),
71
+ // Server Function 사용
72
+ const fn = createServerFn().handler(() => {
73
+ const secret = process.env.SECRET // 서버에서만
286
74
  })
287
75
  ```