@kood/claude-code 0.3.6 → 0.3.8
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 +1 -1
- package/package.json +1 -1
- package/templates/.claude/agents/code-reviewer.md +124 -124
- package/templates/.claude/agents/dependency-manager.md +85 -85
- package/templates/.claude/agents/deployment-validator.md +56 -56
- package/templates/.claude/agents/git-operator.md +64 -64
- package/templates/.claude/agents/implementation-executor.md +95 -95
- package/templates/.claude/agents/ko-to-en-translator.md +74 -0
- package/templates/.claude/agents/lint-fixer.md +78 -78
- package/templates/.claude/agents/refactor-advisor.md +122 -122
- package/templates/.claude/commands/agent-creator.md +185 -185
- package/templates/.claude/commands/bug-fix.md +193 -193
- package/templates/.claude/commands/command-creator.md +54 -54
- package/templates/.claude/commands/docs-creator.md +57 -57
- package/templates/.claude/commands/docs-refactor.md +26 -26
- package/templates/.claude/commands/execute.md +12 -12
- package/templates/.claude/commands/git-all.md +32 -32
- package/templates/.claude/commands/git-session.md +42 -42
- package/templates/.claude/commands/git.md +34 -34
- package/templates/.claude/commands/lint-fix.md +138 -138
- package/templates/.claude/commands/lint-init.md +61 -61
- package/templates/.claude/commands/plan.md +260 -260
- package/templates/.claude/commands/prd.md +24 -24
- package/templates/.claude/commands/pre-deploy.md +109 -109
- package/templates/.claude/commands/refactor.md +147 -147
- package/templates/.claude/commands/version-update.md +17 -17
- package/templates/hono/CLAUDE.md +27 -27
- package/templates/hono/docs/architecture.md +24 -24
- package/templates/hono/docs/deployment/cloudflare.md +18 -18
- package/templates/hono/docs/deployment/docker.md +13 -13
- package/templates/hono/docs/deployment/index.md +19 -19
- package/templates/hono/docs/deployment/railway.md +32 -32
- package/templates/hono/docs/deployment/vercel.md +29 -29
- package/templates/hono/docs/guides/conventions.md +57 -57
- package/templates/hono/docs/guides/env-setup.md +47 -47
- package/templates/hono/docs/guides/getting-started.md +27 -27
- package/templates/hono/docs/library/hono/error-handling.md +11 -11
- package/templates/hono/docs/library/hono/index.md +4 -4
- package/templates/hono/docs/library/hono/middleware.md +18 -18
- package/templates/hono/docs/library/hono/rpc.md +7 -7
- package/templates/hono/docs/library/hono/validation.md +6 -6
- package/templates/hono/docs/library/prisma/cloudflare-d1.md +29 -29
- package/templates/hono/docs/library/prisma/config.md +16 -16
- package/templates/hono/docs/library/prisma/index.md +32 -32
- package/templates/hono/docs/library/t3-env/index.md +22 -22
- package/templates/hono/docs/library/zod/index.md +31 -31
- package/templates/nextjs/CLAUDE.md +228 -0
- package/templates/nextjs/docs/design.md +558 -0
- package/templates/nextjs/docs/guides/conventions.md +343 -0
- package/templates/nextjs/docs/guides/getting-started.md +367 -0
- package/templates/nextjs/docs/guides/routes.md +342 -0
- package/templates/nextjs/docs/library/better-auth/index.md +541 -0
- package/templates/nextjs/docs/library/nextjs/app-router.md +269 -0
- package/templates/nextjs/docs/library/nextjs/caching.md +351 -0
- package/templates/nextjs/docs/library/nextjs/index.md +291 -0
- package/templates/nextjs/docs/library/nextjs/middleware.md +391 -0
- package/templates/nextjs/docs/library/nextjs/route-handlers.md +382 -0
- package/templates/nextjs/docs/library/nextjs/server-actions.md +366 -0
- package/templates/nextjs/docs/library/prisma/cloudflare-d1.md +76 -0
- package/templates/nextjs/docs/library/prisma/config.md +77 -0
- package/templates/nextjs/docs/library/prisma/crud.md +90 -0
- package/templates/nextjs/docs/library/prisma/index.md +73 -0
- package/templates/nextjs/docs/library/prisma/relations.md +69 -0
- package/templates/nextjs/docs/library/prisma/schema.md +98 -0
- package/templates/nextjs/docs/library/prisma/setup.md +49 -0
- package/templates/nextjs/docs/library/prisma/transactions.md +50 -0
- package/templates/nextjs/docs/library/tanstack-query/index.md +66 -0
- package/templates/nextjs/docs/library/tanstack-query/invalidation.md +54 -0
- package/templates/nextjs/docs/library/tanstack-query/optimistic-updates.md +77 -0
- package/templates/nextjs/docs/library/tanstack-query/use-mutation.md +63 -0
- package/templates/nextjs/docs/library/tanstack-query/use-query.md +70 -0
- package/templates/nextjs/docs/library/zod/complex-types.md +61 -0
- package/templates/nextjs/docs/library/zod/index.md +56 -0
- package/templates/nextjs/docs/library/zod/transforms.md +51 -0
- package/templates/nextjs/docs/library/zod/validation.md +70 -0
- package/templates/npx/CLAUDE.md +37 -37
- package/templates/npx/docs/library/commander/index.md +12 -12
- package/templates/npx/docs/library/fs-extra/index.md +9 -9
- package/templates/npx/docs/library/prompts/index.md +3 -3
- package/templates/npx/docs/references/patterns.md +12 -12
- package/templates/tanstack-start/CLAUDE.md +53 -49
- package/templates/tanstack-start/docs/architecture.md +128 -128
- package/templates/tanstack-start/docs/design.md +169 -169
- package/templates/tanstack-start/docs/guides/conventions.md +43 -43
- package/templates/tanstack-start/docs/guides/env-setup.md +35 -35
- package/templates/tanstack-start/docs/guides/getting-started.md +19 -19
- package/templates/tanstack-start/docs/guides/hooks.md +63 -35
- package/templates/tanstack-start/docs/guides/routes.md +61 -42
- package/templates/tanstack-start/docs/guides/services.md +45 -45
- package/templates/tanstack-start/docs/library/better-auth/index.md +68 -68
- package/templates/tanstack-start/docs/library/prisma/cloudflare-d1.md +19 -19
- package/templates/tanstack-start/docs/library/prisma/config.md +16 -16
- package/templates/tanstack-start/docs/library/prisma/crud.md +17 -17
- package/templates/tanstack-start/docs/library/prisma/index.md +17 -17
- package/templates/tanstack-start/docs/library/prisma/relations.md +16 -16
- package/templates/tanstack-start/docs/library/prisma/schema.md +23 -23
- package/templates/tanstack-start/docs/library/prisma/setup.md +6 -6
- package/templates/tanstack-start/docs/library/prisma/transactions.md +10 -10
- package/templates/tanstack-start/docs/library/t3-env/index.md +21 -160
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +6 -6
- package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +19 -19
- package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +4 -4
- package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +14 -14
- package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +21 -21
- package/templates/tanstack-start/docs/library/tanstack-router/error-handling.md +9 -9
- package/templates/tanstack-start/docs/library/tanstack-router/hooks.md +11 -11
- package/templates/tanstack-start/docs/library/tanstack-router/index.md +18 -18
- package/templates/tanstack-start/docs/library/tanstack-router/navigation.md +17 -17
- package/templates/tanstack-start/docs/library/tanstack-router/route-context.md +5 -5
- package/templates/tanstack-start/docs/library/tanstack-router/search-params.md +10 -10
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +8 -8
- package/templates/tanstack-start/docs/library/tanstack-start/index.md +15 -15
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +9 -9
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +6 -6
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +18 -18
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +4 -4
- package/templates/tanstack-start/docs/library/zod/complex-types.md +11 -11
- package/templates/tanstack-start/docs/library/zod/index.md +8 -8
- package/templates/tanstack-start/docs/library/zod/transforms.md +11 -11
- package/templates/tanstack-start/docs/library/zod/validation.md +9 -9
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# Conventions
|
|
2
|
+
|
|
3
|
+
> Code writing rules
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## File Names
|
|
8
|
+
|
|
9
|
+
| Type | Rule | Example |
|
|
10
|
+
|------|------|---------|
|
|
11
|
+
| Component | kebab-case | `user-profile.tsx` |
|
|
12
|
+
| Route | Next.js convention | `page.tsx`, `layout.tsx`, `[id]/page.tsx` |
|
|
13
|
+
| Server Actions | kebab-case | `create-post.ts`, `posts.ts` |
|
|
14
|
+
| Utilities | kebab-case | `format-date.ts` |
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## TypeScript
|
|
19
|
+
|
|
20
|
+
### Variable Declaration
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// ✅ Prefer const
|
|
24
|
+
const user = { name: "Alice" }
|
|
25
|
+
const posts = await prisma.post.findMany()
|
|
26
|
+
|
|
27
|
+
// ❌ Minimize let
|
|
28
|
+
let count = 0 // Only when mutation needed
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Function Declaration
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// ✅ const arrow function + explicit return type
|
|
35
|
+
const getUser = async (id: string): Promise<User> => {
|
|
36
|
+
return prisma.user.findUnique({ where: { id } })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ❌ function keyword (except export default)
|
|
40
|
+
function getUser(id: string) {
|
|
41
|
+
return prisma.user.findUnique({ where: { id } })
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Type Definition
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// ✅ interface (objects)
|
|
49
|
+
interface User {
|
|
50
|
+
id: string
|
|
51
|
+
name: string
|
|
52
|
+
email: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ✅ type (unions, others)
|
|
56
|
+
type Status = "active" | "inactive"
|
|
57
|
+
type UserOrNull = User | null
|
|
58
|
+
|
|
59
|
+
// ❌ No any → use unknown
|
|
60
|
+
const data: unknown = JSON.parse(jsonString)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Import Order
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// 1. External libraries
|
|
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. Relative paths
|
|
77
|
+
import { UserProfile } from "./-components/user-profile"
|
|
78
|
+
|
|
79
|
+
// 4. Types (separate)
|
|
80
|
+
import type { User } from "@/types"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Components
|
|
86
|
+
|
|
87
|
+
### Server Component (default)
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// ✅ async function + direct data fetching
|
|
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" + interactivity
|
|
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
|
+
### File Top
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// ✅ "use server" + multiple functions
|
|
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 Validation
|
|
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
|
+
## Comments
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// ✅ Comments per code block
|
|
158
|
+
// Check user authentication
|
|
159
|
+
const session = await auth.api.getSession({ headers: headers() })
|
|
160
|
+
if (!session?.user) redirect("/login")
|
|
161
|
+
|
|
162
|
+
// Fetch posts
|
|
163
|
+
const posts = await prisma.post.findMany({
|
|
164
|
+
where: { userId: session.user.id },
|
|
165
|
+
orderBy: { createdAt: "desc" },
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
// ❌ Comments on every line
|
|
169
|
+
const session = await auth.api.getSession({ headers: headers() }) // Get session
|
|
170
|
+
if (!session?.user) redirect("/login") // Redirect to login
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Prisma
|
|
176
|
+
|
|
177
|
+
### Multi-File Structure
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
prisma/schema/
|
|
181
|
+
├── +base.prisma # datasource, generator
|
|
182
|
+
├── +enum.prisma # all enums
|
|
183
|
+
└── user.prisma # User model
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Required Comments
|
|
187
|
+
|
|
188
|
+
```prisma
|
|
189
|
+
/// User
|
|
190
|
+
model User {
|
|
191
|
+
id String @id @default(cuid()) /// Unique ID
|
|
192
|
+
email String @unique /// Email (unique)
|
|
193
|
+
name String? /// Name (optional)
|
|
194
|
+
role Role @default(USER) /// Role
|
|
195
|
+
createdAt DateTime @default(now()) /// Created at
|
|
196
|
+
updatedAt DateTime @updatedAt /// Updated at
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// Role
|
|
200
|
+
enum Role {
|
|
201
|
+
USER /// Regular user
|
|
202
|
+
ADMIN /// Administrator
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Folder Structure
|
|
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/ # Page-specific (required)
|
|
220
|
+
│ │ ├── stats.tsx
|
|
221
|
+
│ │ └── chart.tsx
|
|
222
|
+
│ └── settings/
|
|
223
|
+
│ └── page.tsx
|
|
224
|
+
└── _components/ # Shared Client Components
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Rules:**
|
|
228
|
+
- `-components/` - Page-specific (cannot import from outside)
|
|
229
|
+
- `_components/` - Shared (not included in routes)
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Custom Hook Order
|
|
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
|
+
## Best Practices
|
|
271
|
+
|
|
272
|
+
### ✅ DO
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
// 1. Explicit types
|
|
276
|
+
const getUser = async (id: string): Promise<User> => { /* ... */ }
|
|
277
|
+
|
|
278
|
+
// 2. Zod validation
|
|
279
|
+
const schema = z.object({ email: z.email() })
|
|
280
|
+
const parsed = schema.parse(data)
|
|
281
|
+
|
|
282
|
+
// 3. Error handling
|
|
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. Using any
|
|
299
|
+
const data: any = await fetchData() // ❌
|
|
300
|
+
|
|
301
|
+
// 2. Missing validation
|
|
302
|
+
const email = formData.get("email") // ❌ Need Zod validation
|
|
303
|
+
await createUser({ email })
|
|
304
|
+
|
|
305
|
+
// 3. Ignoring errors
|
|
306
|
+
await prisma.post.create({ data }) // ❌ Need try-catch
|
|
307
|
+
|
|
308
|
+
// 4. Missing cache invalidation
|
|
309
|
+
await prisma.post.create({ data }) // ❌ Need revalidatePath
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Git Commits
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
# ✅ Single line, with prefix
|
|
318
|
+
git commit -m "feat: add post creation feature"
|
|
319
|
+
git commit -m "fix: resolve login bug"
|
|
320
|
+
|
|
321
|
+
# ❌ Multiple lines, emojis, AI markers
|
|
322
|
+
git commit -m "feat: add post creation feature
|
|
323
|
+
|
|
324
|
+
Detailed description...
|
|
325
|
+
|
|
326
|
+
Co-Authored-By: Claude Code <noreply@anthropic.com>" # ❌
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Prefixes:**
|
|
330
|
+
- `feat` - New feature
|
|
331
|
+
- `fix` - Bug fix
|
|
332
|
+
- `refactor` - Refactoring
|
|
333
|
+
- `style` - Code style
|
|
334
|
+
- `docs` - Documentation
|
|
335
|
+
- `test` - Tests
|
|
336
|
+
- `chore` - Others
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## References
|
|
341
|
+
|
|
342
|
+
- [Next.js Official Docs](https://nextjs.org/docs)
|
|
343
|
+
- [TypeScript Official Docs](https://www.typescriptlang.org/)
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
> Start with Next.js 15
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Create Project
|
|
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
|
+
## Required Dependencies
|
|
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
|
+
## Folder Structure
|
|
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/ # Page-specific
|
|
57
|
+
│ └── api/
|
|
58
|
+
│ └── auth/
|
|
59
|
+
│ └── [...all]/
|
|
60
|
+
│ └── route.ts
|
|
61
|
+
├── actions/ # Server Actions (shared)
|
|
62
|
+
│ ├── posts.ts
|
|
63
|
+
│ └── users.ts
|
|
64
|
+
├── components/
|
|
65
|
+
│ └── ui/ # UI components
|
|
66
|
+
├── lib/
|
|
67
|
+
│ ├── auth.ts # Better Auth setup
|
|
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
|
+
## Environment Variables
|
|
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
|
+
# Social login (optional)
|
|
87
|
+
GOOGLE_CLIENT_ID="..."
|
|
88
|
+
GOOGLE_CLIENT_SECRET="..."
|
|
89
|
+
GITHUB_CLIENT_ID="..."
|
|
90
|
+
GITHUB_CLIENT_SECRET="..."
|
|
91
|
+
|
|
92
|
+
# Public client variables
|
|
93
|
+
NEXT_PUBLIC_APP_URL="http://localhost:3000"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Prisma Setup
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Initialize
|
|
102
|
+
npx prisma init
|
|
103
|
+
|
|
104
|
+
# Create schema
|
|
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
|
+
# Sync database
|
|
143
|
+
npx prisma db push
|
|
144
|
+
|
|
145
|
+
# Generate 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 Setup
|
|
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 Setup
|
|
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 minute
|
|
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="en">
|
|
260
|
+
<body>
|
|
261
|
+
<Providers>{children}</Providers>
|
|
262
|
+
</body>
|
|
263
|
+
</html>
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## First 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
|
+
## First Page (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
|
+
## First Form (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
|
+
## Next Steps
|
|
363
|
+
|
|
364
|
+
- [Conventions](conventions.md) - Code conventions
|
|
365
|
+
- [Routes](routes.md) - Routing patterns
|
|
366
|
+
- [Server Actions](server-actions.md) - Server Actions patterns
|
|
367
|
+
- [Client Components](client-components.md) - Client Components patterns
|