@kood/claude-code 0.3.6 → 0.3.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 (34) hide show
  1. package/dist/index.js +1 -1
  2. package/package.json +1 -1
  3. package/templates/nextjs/CLAUDE.md +228 -0
  4. package/templates/nextjs/docs/design.md +558 -0
  5. package/templates/nextjs/docs/guides/conventions.md +343 -0
  6. package/templates/nextjs/docs/guides/getting-started.md +367 -0
  7. package/templates/nextjs/docs/guides/routes.md +342 -0
  8. package/templates/nextjs/docs/library/better-auth/index.md +541 -0
  9. package/templates/nextjs/docs/library/nextjs/app-router.md +269 -0
  10. package/templates/nextjs/docs/library/nextjs/caching.md +351 -0
  11. package/templates/nextjs/docs/library/nextjs/index.md +291 -0
  12. package/templates/nextjs/docs/library/nextjs/middleware.md +391 -0
  13. package/templates/nextjs/docs/library/nextjs/route-handlers.md +382 -0
  14. package/templates/nextjs/docs/library/nextjs/server-actions.md +366 -0
  15. package/templates/nextjs/docs/library/prisma/cloudflare-d1.md +76 -0
  16. package/templates/nextjs/docs/library/prisma/config.md +77 -0
  17. package/templates/nextjs/docs/library/prisma/crud.md +90 -0
  18. package/templates/nextjs/docs/library/prisma/index.md +73 -0
  19. package/templates/nextjs/docs/library/prisma/relations.md +69 -0
  20. package/templates/nextjs/docs/library/prisma/schema.md +98 -0
  21. package/templates/nextjs/docs/library/prisma/setup.md +49 -0
  22. package/templates/nextjs/docs/library/prisma/transactions.md +50 -0
  23. package/templates/nextjs/docs/library/tanstack-query/index.md +66 -0
  24. package/templates/nextjs/docs/library/tanstack-query/invalidation.md +54 -0
  25. package/templates/nextjs/docs/library/tanstack-query/optimistic-updates.md +77 -0
  26. package/templates/nextjs/docs/library/tanstack-query/use-mutation.md +63 -0
  27. package/templates/nextjs/docs/library/tanstack-query/use-query.md +70 -0
  28. package/templates/nextjs/docs/library/zod/complex-types.md +61 -0
  29. package/templates/nextjs/docs/library/zod/index.md +56 -0
  30. package/templates/nextjs/docs/library/zod/transforms.md +51 -0
  31. package/templates/nextjs/docs/library/zod/validation.md +70 -0
  32. package/templates/tanstack-start/CLAUDE.md +7 -3
  33. package/templates/tanstack-start/docs/guides/hooks.md +28 -0
  34. package/templates/tanstack-start/docs/guides/routes.md +29 -10
@@ -0,0 +1,343 @@
1
+ # Conventions
2
+
3
+ > 코드 작성 규칙
4
+
5
+ ---
6
+
7
+ ## 파일명
8
+
9
+ | 유형 | 규칙 | 예시 |
10
+ |------|------|------|
11
+ | 컴포넌트 | kebab-case | `user-profile.tsx` |
12
+ | 라우트 | Next.js 규칙 | `page.tsx`, `layout.tsx`, `[id]/page.tsx` |
13
+ | Server Actions | kebab-case | `create-post.ts`, `posts.ts` |
14
+ | 유틸리티 | kebab-case | `format-date.ts` |
15
+
16
+ ---
17
+
18
+ ## TypeScript
19
+
20
+ ### 변수 선언
21
+
22
+ ```typescript
23
+ // ✅ const 우선
24
+ const user = { name: "Alice" }
25
+ const posts = await prisma.post.findMany()
26
+
27
+ // ❌ let 최소화
28
+ let count = 0 // 변경 필요시만
29
+ ```
30
+
31
+ ### 함수 선언
32
+
33
+ ```typescript
34
+ // ✅ const 화살표 함수 + 명시적 return type
35
+ const getUser = async (id: string): Promise<User> => {
36
+ return prisma.user.findUnique({ where: { id } })
37
+ }
38
+
39
+ // ❌ function 키워드 (export default 제외)
40
+ function getUser(id: string) {
41
+ return prisma.user.findUnique({ where: { id } })
42
+ }
43
+ ```
44
+
45
+ ### 타입 정의
46
+
47
+ ```typescript
48
+ // ✅ interface (객체)
49
+ interface User {
50
+ id: string
51
+ name: string
52
+ email: string
53
+ }
54
+
55
+ // ✅ type (유니온, 기타)
56
+ type Status = "active" | "inactive"
57
+ type UserOrNull = User | null
58
+
59
+ // ❌ any 금지 → unknown 사용
60
+ const data: unknown = JSON.parse(jsonString)
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Import 순서
66
+
67
+ ```typescript
68
+ // 1. 외부 라이브러리
69
+ import { useState } from "react"
70
+ import { useQuery } from "@tanstack/react-query"
71
+
72
+ // 2. @/ alias
73
+ import { Button } from "@/components/ui/button"
74
+ import { prisma } from "@/database/prisma"
75
+
76
+ // 3. 상대경로
77
+ import { UserProfile } from "./-components/user-profile"
78
+
79
+ // 4. 타입 (분리)
80
+ import type { User } from "@/types"
81
+ ```
82
+
83
+ ---
84
+
85
+ ## 컴포넌트
86
+
87
+ ### Server Component (기본)
88
+
89
+ ```typescript
90
+ // ✅ async 함수 + 직접 데이터 페칭
91
+ export default async function PostsPage() {
92
+ const posts = await prisma.post.findMany()
93
+ return <PostsList posts={posts} />
94
+ }
95
+ ```
96
+
97
+ ### Client Component
98
+
99
+ ```typescript
100
+ // ✅ "use client" + 상호작용
101
+ "use client"
102
+
103
+ import { useState } from "react"
104
+
105
+ export function Counter() {
106
+ const [count, setCount] = useState(0)
107
+ return <button onClick={() => setCount(count + 1)}>{count}</button>
108
+ }
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Server Actions
114
+
115
+ ### 파일 상단
116
+
117
+ ```typescript
118
+ // ✅ "use server" + 여러 함수
119
+ "use server"
120
+
121
+ export async function createPost(formData: FormData) {
122
+ // ...
123
+ }
124
+
125
+ export async function deletePost(id: string) {
126
+ // ...
127
+ }
128
+ ```
129
+
130
+ ### Zod 검증
131
+
132
+ ```typescript
133
+ "use server"
134
+
135
+ import { z } from "zod"
136
+
137
+ const schema = z.object({
138
+ title: z.string().min(1),
139
+ content: z.string(),
140
+ })
141
+
142
+ export async function createPost(formData: FormData) {
143
+ const parsed = schema.parse({
144
+ title: formData.get("title"),
145
+ content: formData.get("content"),
146
+ })
147
+
148
+ // ...
149
+ }
150
+ ```
151
+
152
+ ---
153
+
154
+ ## 주석
155
+
156
+ ```typescript
157
+ // ✅ 코드 묶음 단위로 한글 주석
158
+ // 사용자 인증 체크
159
+ const session = await auth.api.getSession({ headers: headers() })
160
+ if (!session?.user) redirect("/login")
161
+
162
+ // 게시글 조회
163
+ const posts = await prisma.post.findMany({
164
+ where: { userId: session.user.id },
165
+ orderBy: { createdAt: "desc" },
166
+ })
167
+
168
+ // ❌ 모든 줄마다 주석
169
+ const session = await auth.api.getSession({ headers: headers() }) // 세션 조회
170
+ if (!session?.user) redirect("/login") // 로그인 리다이렉트
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Prisma
176
+
177
+ ### Multi-File 구조
178
+
179
+ ```
180
+ prisma/schema/
181
+ ├── +base.prisma # datasource, generator
182
+ ├── +enum.prisma # 모든 enum
183
+ └── user.prisma # User 모델
184
+ ```
185
+
186
+ ### 한글 주석 필수
187
+
188
+ ```prisma
189
+ /// 사용자
190
+ model User {
191
+ id String @id @default(cuid()) /// 고유 ID
192
+ email String @unique /// 이메일 (고유)
193
+ name String? /// 이름 (옵션)
194
+ role Role @default(USER) /// 역할
195
+ createdAt DateTime @default(now()) /// 생성일
196
+ updatedAt DateTime @updatedAt /// 수정일
197
+ }
198
+
199
+ /// 역할
200
+ enum Role {
201
+ USER /// 일반 사용자
202
+ ADMIN /// 관리자
203
+ }
204
+ ```
205
+
206
+ ---
207
+
208
+ ## 폴더 구조
209
+
210
+ ```
211
+ app/
212
+ ├── (auth)/ # Route Group
213
+ │ ├── login/
214
+ │ │ └── page.tsx
215
+ │ └── signup/
216
+ │ └── page.tsx
217
+ ├── dashboard/
218
+ │ ├── page.tsx
219
+ │ ├── -components/ # 페이지 전용 (필수)
220
+ │ │ ├── stats.tsx
221
+ │ │ └── chart.tsx
222
+ │ └── settings/
223
+ │ └── page.tsx
224
+ └── _components/ # 공통 Client Components
225
+ ```
226
+
227
+ **규칙:**
228
+ - `-components/` - 페이지 전용 (밖에서 import 불가)
229
+ - `_components/` - 공통 (라우트에 포함 안됨)
230
+
231
+ ---
232
+
233
+ ## Custom Hook 순서
234
+
235
+ ```typescript
236
+ "use client"
237
+
238
+ export function useExample() {
239
+ // 1. State (useState, zustand)
240
+ const [count, setCount] = useState(0)
241
+
242
+ // 2. Global Hooks
243
+ const router = useRouter()
244
+ const params = useParams()
245
+ const searchParams = useSearchParams()
246
+
247
+ // 3. React Query
248
+ const { data: posts } = useQuery({ queryKey: ["posts"], queryFn: getPosts })
249
+ const mutation = useMutation({ mutationFn: createPost })
250
+
251
+ // 4. Event Handlers
252
+ const handleClick = () => {
253
+ setCount(count + 1)
254
+ }
255
+
256
+ // 5. useMemo
257
+ const total = useMemo(() => posts?.length || 0, [posts])
258
+
259
+ // 6. useEffect
260
+ useEffect(() => {
261
+ console.log(count)
262
+ }, [count])
263
+
264
+ return { count, handleClick, total }
265
+ }
266
+ ```
267
+
268
+ ---
269
+
270
+ ## 베스트 프랙티스
271
+
272
+ ### ✅ DO
273
+
274
+ ```typescript
275
+ // 1. 명시적 타입
276
+ const getUser = async (id: string): Promise<User> => { /* ... */ }
277
+
278
+ // 2. Zod 검증
279
+ const schema = z.object({ email: z.email() })
280
+ const parsed = schema.parse(data)
281
+
282
+ // 3. 에러 처리
283
+ try {
284
+ await prisma.post.create({ data })
285
+ } catch (error) {
286
+ console.error("Error creating post:", error)
287
+ throw error
288
+ }
289
+
290
+ // 4. revalidatePath
291
+ await prisma.post.create({ data })
292
+ revalidatePath("/posts")
293
+ ```
294
+
295
+ ### ❌ DON'T
296
+
297
+ ```typescript
298
+ // 1. any 사용
299
+ const data: any = await fetchData() // ❌
300
+
301
+ // 2. 검증 누락
302
+ const email = formData.get("email") // ❌ Zod 검증 필요
303
+ await createUser({ email })
304
+
305
+ // 3. 에러 무시
306
+ await prisma.post.create({ data }) // ❌ try-catch 필요
307
+
308
+ // 4. 캐시 무효화 누락
309
+ await prisma.post.create({ data }) // ❌ revalidatePath 필요
310
+ ```
311
+
312
+ ---
313
+
314
+ ## Git 커밋
315
+
316
+ ```bash
317
+ # ✅ 한 줄, prefix 사용
318
+ git commit -m "feat: 게시글 생성 기능 추가"
319
+ git commit -m "fix: 로그인 버그 수정"
320
+
321
+ # ❌ 여러 줄, 이모지, AI 표시
322
+ git commit -m "feat: 게시글 생성 기능 추가
323
+
324
+ 상세 설명...
325
+
326
+ Co-Authored-By: Claude Code <noreply@anthropic.com>" # ❌
327
+ ```
328
+
329
+ **Prefix:**
330
+ - `feat` - 새 기능
331
+ - `fix` - 버그 수정
332
+ - `refactor` - 리팩토링
333
+ - `style` - 코드 스타일
334
+ - `docs` - 문서
335
+ - `test` - 테스트
336
+ - `chore` - 기타
337
+
338
+ ---
339
+
340
+ ## 참조
341
+
342
+ - [Next.js 공식 문서](https://nextjs.org/docs)
343
+ - [TypeScript 공식 문서](https://www.typescriptlang.org/)
@@ -0,0 +1,367 @@
1
+ # Getting Started
2
+
3
+ > Next.js 15 프로젝트 시작하기
4
+
5
+ ---
6
+
7
+ ## 프로젝트 생성
8
+
9
+ ```bash
10
+ npx create-next-app@latest my-app \
11
+ --typescript \
12
+ --tailwind \
13
+ --app \
14
+ --src-dir \
15
+ --import-alias "@/*"
16
+
17
+ cd my-app
18
+ npm run dev
19
+ ```
20
+
21
+ ---
22
+
23
+ ## 필수 의존성
24
+
25
+ ```bash
26
+ # Database
27
+ npm install prisma @prisma/client
28
+ npm install -D prisma
29
+
30
+ # Validation
31
+ npm install zod
32
+
33
+ # Auth
34
+ npm install better-auth
35
+
36
+ # Data Fetching
37
+ npm install @tanstack/react-query
38
+ ```
39
+
40
+ ---
41
+
42
+ ## 폴더 구조
43
+
44
+ ```
45
+ src/
46
+ ├── app/
47
+ │ ├── layout.tsx # Root layout
48
+ │ ├── page.tsx # Home
49
+ │ ├── (auth)/
50
+ │ │ ├── login/
51
+ │ │ │ └── page.tsx
52
+ │ │ └── signup/
53
+ │ │ └── page.tsx
54
+ │ ├── dashboard/
55
+ │ │ ├── page.tsx
56
+ │ │ └── -components/ # 페이지 전용
57
+ │ └── api/
58
+ │ └── auth/
59
+ │ └── [...all]/
60
+ │ └── route.ts
61
+ ├── actions/ # Server Actions (공통)
62
+ │ ├── posts.ts
63
+ │ └── users.ts
64
+ ├── components/
65
+ │ └── ui/ # UI 컴포넌트
66
+ ├── lib/
67
+ │ ├── auth.ts # Better Auth 설정
68
+ │ ├── auth-client.ts # Auth Client
69
+ │ ├── prisma.ts # Prisma Client
70
+ │ └── query-client.ts # React Query Client
71
+ ├── database/
72
+ │ └── prisma.ts
73
+ └── middleware.ts
74
+ ```
75
+
76
+ ---
77
+
78
+ ## 환경 변수
79
+
80
+ ```bash
81
+ # .env.local
82
+ DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
83
+ BETTER_AUTH_SECRET="your-secret-key"
84
+ BETTER_AUTH_URL="http://localhost:3000"
85
+
86
+ # 소셜 로그인 (옵션)
87
+ GOOGLE_CLIENT_ID="..."
88
+ GOOGLE_CLIENT_SECRET="..."
89
+ GITHUB_CLIENT_ID="..."
90
+ GITHUB_CLIENT_SECRET="..."
91
+
92
+ # 클라이언트 공개 변수
93
+ NEXT_PUBLIC_APP_URL="http://localhost:3000"
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Prisma 설정
99
+
100
+ ```bash
101
+ # 초기화
102
+ npx prisma init
103
+
104
+ # 스키마 생성
105
+ mkdir -p prisma/schema
106
+ ```
107
+
108
+ ```prisma
109
+ // prisma/schema/+base.prisma
110
+ datasource db {
111
+ provider = "postgresql"
112
+ url = env("DATABASE_URL")
113
+ }
114
+
115
+ generator client {
116
+ provider = "prisma-client-js"
117
+ output = "../node_modules/.prisma/client"
118
+ }
119
+ ```
120
+
121
+ ```prisma
122
+ // prisma/schema/+enum.prisma
123
+ enum Role {
124
+ USER
125
+ ADMIN
126
+ }
127
+ ```
128
+
129
+ ```prisma
130
+ // prisma/schema/user.prisma
131
+ model User {
132
+ id String @id @default(cuid())
133
+ email String @unique
134
+ name String?
135
+ role Role @default(USER)
136
+ createdAt DateTime @default(now())
137
+ updatedAt DateTime @updatedAt
138
+ }
139
+ ```
140
+
141
+ ```bash
142
+ # DB 동기화
143
+ npx prisma db push
144
+
145
+ # Client 생성
146
+ npx prisma generate
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Prisma Client
152
+
153
+ ```typescript
154
+ // src/database/prisma.ts
155
+ import { PrismaClient } from "@prisma/client"
156
+
157
+ const globalForPrisma = globalThis as unknown as {
158
+ prisma: PrismaClient | undefined
159
+ }
160
+
161
+ export const prisma = globalForPrisma.prisma ?? new PrismaClient()
162
+
163
+ if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Better Auth 설정
169
+
170
+ ```typescript
171
+ // src/lib/auth.ts
172
+ import { betterAuth } from "better-auth"
173
+ import { prismaAdapter } from "better-auth/adapters/prisma"
174
+ import { prisma } from "@/database/prisma"
175
+
176
+ export const auth = betterAuth({
177
+ database: prismaAdapter(prisma, { provider: "postgresql" }),
178
+ emailAndPassword: { enabled: true },
179
+ socialProviders: {
180
+ google: {
181
+ clientId: process.env.GOOGLE_CLIENT_ID!,
182
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
183
+ },
184
+ },
185
+ })
186
+ ```
187
+
188
+ ```typescript
189
+ // src/lib/auth-client.ts
190
+ import { createAuthClient } from "better-auth/react"
191
+
192
+ export const authClient = createAuthClient({
193
+ baseURL: process.env.NEXT_PUBLIC_APP_URL!,
194
+ })
195
+ ```
196
+
197
+ ```typescript
198
+ // app/api/auth/[...all]/route.ts
199
+ import { auth } from "@/lib/auth"
200
+
201
+ export const GET = (request: Request) => auth.handler(request)
202
+ export const POST = (request: Request) => auth.handler(request)
203
+ ```
204
+
205
+ ---
206
+
207
+ ## React Query 설정
208
+
209
+ ```typescript
210
+ // src/lib/query-client.ts
211
+ import { QueryClient } from "@tanstack/react-query"
212
+
213
+ export function makeQueryClient() {
214
+ return new QueryClient({
215
+ defaultOptions: {
216
+ queries: {
217
+ staleTime: 60 * 1000, // 1분
218
+ },
219
+ },
220
+ })
221
+ }
222
+
223
+ let browserQueryClient: QueryClient | undefined = undefined
224
+
225
+ export function getQueryClient() {
226
+ if (typeof window === "undefined") {
227
+ return makeQueryClient()
228
+ } else {
229
+ if (!browserQueryClient) browserQueryClient = makeQueryClient()
230
+ return browserQueryClient
231
+ }
232
+ }
233
+ ```
234
+
235
+ ```typescript
236
+ // app/providers.tsx
237
+ "use client"
238
+
239
+ import { QueryClientProvider } from "@tanstack/react-query"
240
+ import { getQueryClient } from "@/lib/query-client"
241
+
242
+ export function Providers({ children }: { children: React.ReactNode }) {
243
+ const queryClient = getQueryClient()
244
+
245
+ return (
246
+ <QueryClientProvider client={queryClient}>
247
+ {children}
248
+ </QueryClientProvider>
249
+ )
250
+ }
251
+ ```
252
+
253
+ ```typescript
254
+ // app/layout.tsx
255
+ import { Providers } from "./providers"
256
+
257
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
258
+ return (
259
+ <html lang="ko">
260
+ <body>
261
+ <Providers>{children}</Providers>
262
+ </body>
263
+ </html>
264
+ )
265
+ }
266
+ ```
267
+
268
+ ---
269
+
270
+ ## 첫 Server Action
271
+
272
+ ```typescript
273
+ // actions/posts.ts
274
+ "use server"
275
+
276
+ import { z } from "zod"
277
+ import { prisma } from "@/database/prisma"
278
+ import { revalidatePath } from "next/cache"
279
+
280
+ const createPostSchema = z.object({
281
+ title: z.string().min(1),
282
+ content: z.string(),
283
+ })
284
+
285
+ export async function createPost(formData: FormData) {
286
+ const parsed = createPostSchema.parse({
287
+ title: formData.get("title"),
288
+ content: formData.get("content"),
289
+ })
290
+
291
+ const post = await prisma.post.create({ data: parsed })
292
+ revalidatePath("/posts")
293
+ return post
294
+ }
295
+ ```
296
+
297
+ ---
298
+
299
+ ## 첫 페이지 (Server Component)
300
+
301
+ ```typescript
302
+ // app/posts/page.tsx
303
+ import { prisma } from "@/database/prisma"
304
+
305
+ export default async function PostsPage() {
306
+ const posts = await prisma.post.findMany()
307
+
308
+ return (
309
+ <div>
310
+ <h1>Posts</h1>
311
+ <ul>
312
+ {posts.map(post => (
313
+ <li key={post.id}>{post.title}</li>
314
+ ))}
315
+ </ul>
316
+ </div>
317
+ )
318
+ }
319
+ ```
320
+
321
+ ---
322
+
323
+ ## 첫 폼 (Client Component)
324
+
325
+ ```typescript
326
+ // app/posts/_components/create-post-form.tsx
327
+ "use client"
328
+
329
+ import { useMutation, useQueryClient } from "@tanstack/react-query"
330
+ import { createPost } from "@/actions/posts"
331
+
332
+ export function CreatePostForm() {
333
+ const queryClient = useQueryClient()
334
+
335
+ const mutation = useMutation({
336
+ mutationFn: createPost,
337
+ onSuccess: () => {
338
+ queryClient.invalidateQueries({ queryKey: ["posts"] })
339
+ },
340
+ })
341
+
342
+ return (
343
+ <form
344
+ onSubmit={(e) => {
345
+ e.preventDefault()
346
+ const formData = new FormData(e.currentTarget)
347
+ mutation.mutate(formData)
348
+ }}
349
+ >
350
+ <input name="title" placeholder="Title" required />
351
+ <textarea name="content" placeholder="Content" required />
352
+ <button type="submit" disabled={mutation.isPending}>
353
+ {mutation.isPending ? "Creating..." : "Create Post"}
354
+ </button>
355
+ </form>
356
+ )
357
+ }
358
+ ```
359
+
360
+ ---
361
+
362
+ ## 다음 단계
363
+
364
+ - [Conventions](conventions.md) - 코드 컨벤션
365
+ - [Routes](routes.md) - 라우팅 패턴
366
+ - [Server Actions](server-actions.md) - Server Actions 패턴
367
+ - [Client Components](client-components.md) - Client Components 패턴