@kood/claude-code 0.5.4 → 0.5.6
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 +6 -2
- package/package.json +1 -1
- package/templates/.claude/agents/document-writer.md +2 -2
- package/templates/.claude/skills/docs-creator/SKILL.md +2 -2
- package/templates/.claude/skills/docs-refactor/SKILL.md +1 -1
- package/templates/.claude/skills/plan/SKILL.md +15 -15
- package/templates/.claude/skills/ralph/SKILL.md +425 -72
- package/templates/hono/CLAUDE.md +28 -28
- 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 +54 -54
- package/templates/nextjs/docs/architecture.md +146 -146
- package/templates/nextjs/docs/design.md +183 -183
- package/templates/nextjs/docs/guides/conventions.md +86 -86
- package/templates/nextjs/docs/guides/getting-started.md +28 -28
- package/templates/nextjs/docs/guides/routes.md +32 -32
- package/templates/nextjs/docs/library/better-auth/index.md +70 -70
- package/templates/nextjs/docs/library/nextjs/app-router.md +43 -43
- package/templates/nextjs/docs/library/nextjs/caching.md +73 -73
- package/templates/nextjs/docs/library/nextjs/index.md +51 -51
- package/templates/nextjs/docs/library/nextjs/middleware.md +41 -41
- package/templates/nextjs/docs/library/nextjs/route-handlers.md +31 -31
- package/templates/nextjs/docs/library/nextjs/server-actions.md +34 -34
- package/templates/nextjs/docs/library/prisma/cloudflare-d1.md +20 -20
- package/templates/nextjs/docs/library/prisma/config.md +18 -18
- package/templates/nextjs/docs/library/prisma/crud.md +17 -17
- package/templates/nextjs/docs/library/prisma/index.md +18 -18
- package/templates/nextjs/docs/library/prisma/relations.md +16 -16
- package/templates/nextjs/docs/library/prisma/schema.md +23 -23
- package/templates/nextjs/docs/library/prisma/setup.md +6 -6
- package/templates/nextjs/docs/library/prisma/transactions.md +10 -10
- package/templates/nextjs/docs/library/tanstack-query/index.md +6 -6
- package/templates/nextjs/docs/library/tanstack-query/invalidation.md +20 -20
- package/templates/nextjs/docs/library/tanstack-query/optimistic-updates.md +4 -4
- package/templates/nextjs/docs/library/tanstack-query/use-mutation.md +15 -15
- package/templates/nextjs/docs/library/tanstack-query/use-query.md +22 -22
- package/templates/nextjs/docs/library/zod/complex-types.md +11 -11
- package/templates/nextjs/docs/library/zod/index.md +8 -8
- package/templates/nextjs/docs/library/zod/transforms.md +11 -11
- package/templates/nextjs/docs/library/zod/validation.md +9 -9
- package/templates/npx/CLAUDE.md +38 -38
- 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 +54 -54
- 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 +45 -45
- package/templates/tanstack-start/docs/guides/routes.md +54 -54
- package/templates/tanstack-start/docs/guides/services.md +45 -45
- 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/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/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/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/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/transforms.md +11 -11
- package/templates/tanstack-start/docs/library/zod/validation.md +9 -9
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Architecture
|
|
2
2
|
|
|
3
|
-
> Next.js App Router
|
|
3
|
+
> Next.js App Router 애플리케이션 아키텍처
|
|
4
4
|
|
|
5
5
|
<instructions>
|
|
6
6
|
@library/nextjs/app-router.md
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
|
|
14
14
|
<forbidden>
|
|
15
15
|
|
|
16
|
-
|
|
|
17
|
-
|
|
18
|
-
|
|
|
19
|
-
| **Route Export** |
|
|
20
|
-
| **API** | Pages Router API (`pages/api/`), API Routes
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
| **Barrel Export** |
|
|
16
|
+
| 분류 | 금지 |
|
|
17
|
+
|------|------|
|
|
18
|
+
| **라우트** | Flat 파일 라우트 (`app/users.tsx`), Pages Router (`pages/`) |
|
|
19
|
+
| **Route Export** | named export (`export const Page`), 잘못된 파일명 (`Users.tsx`) |
|
|
20
|
+
| **API** | Pages Router API (`pages/api/`), API Routes 남용 (Server Actions 사용) |
|
|
21
|
+
| **레이어** | 비즈니스 로직을 app/ 폴더에 직접 작성 |
|
|
22
|
+
| **컴포넌트** | 'use client' 없이 클라이언트 전용 API 사용 |
|
|
23
|
+
| **Barrel Export** | `actions/index.ts` 생성 (Tree Shaking 실패) |
|
|
24
24
|
|
|
25
25
|
</forbidden>
|
|
26
26
|
|
|
@@ -28,19 +28,19 @@
|
|
|
28
28
|
|
|
29
29
|
<required>
|
|
30
30
|
|
|
31
|
-
|
|
|
32
|
-
|
|
33
|
-
|
|
|
34
|
-
| **Route Export** | `export default function Page()`
|
|
35
|
-
|
|
|
36
|
-
| **Route Group** |
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
| **Server Actions** |
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
31
|
+
| 분류 | 필수 |
|
|
32
|
+
|------|------|
|
|
33
|
+
| **라우트 구조** | 페이지마다 폴더 생성 (`app/users/page.tsx`) |
|
|
34
|
+
| **Route Export** | `export default function Page()` 필수 |
|
|
35
|
+
| **계층 구조** | app/ → Server Actions → lib/ → Database |
|
|
36
|
+
| **Route Group** | 목록 → `(main)/`, 관리자 → `(admin)/` |
|
|
37
|
+
| **페이지 전용 폴더** | `_components/`, `_hooks/`, `_actions/` 필수 (줄 수 무관) |
|
|
38
|
+
| **페이지 분리** | 100줄+ → `_components/`, 200줄+ → `_sections/` |
|
|
39
|
+
| **Server Actions** | mutations는 Server Actions 사용 (`'use server'`) |
|
|
40
|
+
| **검증** | Zod 스키마로 입력 검증 |
|
|
41
|
+
| **메타데이터** | `generateMetadata` 또는 `metadata` export |
|
|
42
|
+
| **에러 처리** | `error.tsx` (라우트), `not-found.tsx` (404), `global-error.tsx` (전역) |
|
|
43
|
+
| **타입 안전** | TypeScript strict, Prisma 타입 |
|
|
44
44
|
|
|
45
45
|
</required>
|
|
46
46
|
|
|
@@ -62,12 +62,12 @@
|
|
|
62
62
|
┌─────────────────────────────────────────────────────────────────┐
|
|
63
63
|
│ Next.js Server │
|
|
64
64
|
│ ┌────────────────────────────────────────────────────────────┐ │
|
|
65
|
-
│ │ Server Components (
|
|
65
|
+
│ │ Server Components (기본) │ │
|
|
66
66
|
│ │ app/[route]/page.tsx → Server-side rendering │ │
|
|
67
67
|
│ └────────────────────────────┬───────────────────────────────┘ │
|
|
68
68
|
│ ┌────────────────────────────▼───────────────────────────────┐ │
|
|
69
69
|
│ │ Server Actions │ │
|
|
70
|
-
│ │ 'use server' → DB
|
|
70
|
+
│ │ 'use server' → DB 접근, Mutations, Revalidation │ │
|
|
71
71
|
│ └────────────────────────────┬───────────────────────────────┘ │
|
|
72
72
|
│ ┌────────────────────────────▼───────────────────────────────┐ │
|
|
73
73
|
│ │ Services Layer │ │
|
|
@@ -89,34 +89,34 @@
|
|
|
89
89
|
|
|
90
90
|
<route_export_rule>
|
|
91
91
|
|
|
92
|
-
## Route Export
|
|
92
|
+
## Route Export 규칙
|
|
93
93
|
|
|
94
|
-
> ⚠️ **`export default`
|
|
94
|
+
> ⚠️ **`export default` 필수**
|
|
95
95
|
>
|
|
96
|
-
> Next.js App Router
|
|
96
|
+
> Next.js App Router는 모든 페이지/레이아웃 파일에서 **default export**로 컴포넌트를 내보내야 합니다.
|
|
97
97
|
>
|
|
98
|
-
>
|
|
98
|
+
> 파일명은 Next.js 규칙을 따라야 합니다: `page.tsx`, `layout.tsx`, `loading.tsx`, `error.tsx`, `not-found.tsx`
|
|
99
99
|
|
|
100
|
-
| ❌
|
|
101
|
-
|
|
100
|
+
| ❌ 금지 | ✅ 필수 |
|
|
101
|
+
|--------|--------|
|
|
102
102
|
| `app/users.tsx` | `app/users/page.tsx` |
|
|
103
103
|
| `export const Page = () => {}` | `export default function Page() {}` |
|
|
104
|
-
| `export default Users` (
|
|
104
|
+
| `export default Users` (컴포넌트명 != 파일명 규칙) | `export default function UsersPage() {}` |
|
|
105
105
|
|
|
106
106
|
```typescript
|
|
107
|
-
// ❌
|
|
107
|
+
// ❌ 금지: Flat 파일
|
|
108
108
|
// app/users.tsx
|
|
109
109
|
export default function Users() {
|
|
110
110
|
return <div>Users</div>
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
// ❌
|
|
113
|
+
// ❌ 금지: named export
|
|
114
114
|
// app/users/page.tsx
|
|
115
115
|
export const Page = () => {
|
|
116
116
|
return <div>Users</div>
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
// ✅
|
|
119
|
+
// ✅ 필수: 폴더 + page.tsx + default export
|
|
120
120
|
// app/users/page.tsx
|
|
121
121
|
export default function UsersPage() {
|
|
122
122
|
return <div>Users</div>
|
|
@@ -133,66 +133,66 @@ export default function UsersPage() {
|
|
|
133
133
|
|
|
134
134
|
### 1. Routes Layer (app/)
|
|
135
135
|
|
|
136
|
-
> ⚠️
|
|
136
|
+
> ⚠️ **페이지마다 폴더 생성 필수**
|
|
137
137
|
>
|
|
138
|
-
>
|
|
138
|
+
> 모든 페이지는 **반드시 폴더 구조**로 만들어야 합니다. Flat 파일 방식(`app/users.tsx`)은 금지됩니다.
|
|
139
139
|
>
|
|
140
|
-
>
|
|
140
|
+
> **이유:** `_components/`, `_hooks/`, `_actions/` 등 페이지 전용 리소스를 체계적으로 관리하기 위함입니다.
|
|
141
141
|
>
|
|
142
|
-
> | ❌
|
|
143
|
-
>
|
|
142
|
+
> | ❌ 금지 | ✅ 필수 |
|
|
143
|
+
> |--------|--------|
|
|
144
144
|
> | `app/users.tsx` | `app/users/page.tsx` |
|
|
145
145
|
> | `app/posts.tsx` | `app/(main)/posts/page.tsx` |
|
|
146
146
|
|
|
147
147
|
```
|
|
148
148
|
app/<route-name>/
|
|
149
|
-
├── (main)/ # route group (
|
|
150
|
-
│ ├── page.tsx #
|
|
151
|
-
│ ├── _components/ #
|
|
152
|
-
│ ├── _hooks/ #
|
|
153
|
-
│ ├── _sections/ # UI
|
|
154
|
-
│ └── _tabs/ #
|
|
155
|
-
├── new/ #
|
|
149
|
+
├── (main)/ # route group (목록 페이지, URL에 미포함)
|
|
150
|
+
│ ├── page.tsx # 페이지 컴포넌트
|
|
151
|
+
│ ├── _components/ # 페이지 전용 컴포넌트 (필수)
|
|
152
|
+
│ ├── _hooks/ # 페이지 전용 훅 (필수)
|
|
153
|
+
│ ├── _sections/ # UI 섹션 분리 (200줄+ 페이지)
|
|
154
|
+
│ └── _tabs/ # 탭 콘텐츠 분리
|
|
155
|
+
├── new/ # 생성 페이지 (route group 외부)
|
|
156
156
|
│ └── page.tsx
|
|
157
157
|
├── [id]/ # Dynamic segment
|
|
158
158
|
│ └── page.tsx
|
|
159
|
-
├── layout.tsx #
|
|
160
|
-
├── loading.tsx #
|
|
161
|
-
├── error.tsx #
|
|
162
|
-
└── _actions/ #
|
|
159
|
+
├── layout.tsx # 레이아웃 (하위 경로 공통)
|
|
160
|
+
├── loading.tsx # 로딩 UI (Suspense boundary)
|
|
161
|
+
├── error.tsx # 에러 UI (Error boundary)
|
|
162
|
+
└── _actions/ # 페이지 전용 Server Actions (필수)
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
-
|
|
167
|
-
- Custom
|
|
168
|
-
- Server Actions
|
|
169
|
-
-
|
|
165
|
+
**필수 규칙:**
|
|
166
|
+
- 페이지당 `_components/`, `_hooks/`, `_actions/` 폴더 필수 (줄 수 무관)
|
|
167
|
+
- Custom Hook은 페이지 크기와 무관하게 **반드시** `_hooks/` 폴더에 분리
|
|
168
|
+
- Server Actions는 글로벌(`app/_actions/`) 또는 페이지 전용(`[route]/_actions/`)에 분리
|
|
169
|
+
- 공통 컴포넌트 → `components/ui/`, 페이지 전용 → `[route]/_components/`
|
|
170
170
|
|
|
171
|
-
|
|
|
172
|
-
|
|
173
|
-
| **Route Group** | `(main)/` |
|
|
174
|
-
| **Private Folder** | `_components/` |
|
|
175
|
-
| **_sections/** | 200
|
|
176
|
-
| **_tabs/** |
|
|
177
|
-
| **layout.tsx** |
|
|
171
|
+
| 패턴 | 위치 | 용도 |
|
|
172
|
+
|------|------|------|
|
|
173
|
+
| **Route Group** | `(main)/` | 목록 페이지, URL에 미포함 |
|
|
174
|
+
| **Private Folder** | `_components/` | 라우팅 시스템이 무시 |
|
|
175
|
+
| **_sections/** | 200줄+ | 논리적 섹션 분리 |
|
|
176
|
+
| **_tabs/** | 탭 UI | 탭 콘텐츠 분리 |
|
|
177
|
+
| **layout.tsx** | 레이아웃 | 하위 경로 공통 UI |
|
|
178
178
|
|
|
179
|
-
#### Layout Routes
|
|
179
|
+
#### Layout Routes 패턴
|
|
180
180
|
|
|
181
|
-
> ⚠️ **
|
|
181
|
+
> ⚠️ **layout.tsx로 레이아웃 구성**
|
|
182
182
|
>
|
|
183
|
-
> `layout.tsx
|
|
184
|
-
>
|
|
183
|
+
> `layout.tsx`는 하위 경로의 공통 레이아웃 역할을 합니다.
|
|
184
|
+
> 목록 페이지는 Route Group `(main)/`으로 묶어야 합니다.
|
|
185
185
|
>
|
|
186
|
-
> | ❌
|
|
187
|
-
>
|
|
188
|
-
|
|
186
|
+
> | ❌ 금지 | ✅ 필수 |
|
|
187
|
+
> |--------|--------|
|
|
188
|
+
| `app/auth.tsx` | `app/(auth)/layout.tsx` + `app/(auth)/(main)/page.tsx` |
|
|
189
189
|
|
|
190
190
|
```
|
|
191
191
|
app/
|
|
192
192
|
├── (auth)/
|
|
193
|
-
│ ├── layout.tsx #
|
|
193
|
+
│ ├── layout.tsx # 레이아웃 (children 렌더링)
|
|
194
194
|
│ ├── (main)/
|
|
195
|
-
│ │ └── page.tsx # /auth (
|
|
195
|
+
│ │ └── page.tsx # /auth (메인)
|
|
196
196
|
│ ├── login/
|
|
197
197
|
│ │ └── page.tsx # /auth/login
|
|
198
198
|
│ └── register/
|
|
@@ -200,13 +200,13 @@ app/
|
|
|
200
200
|
```
|
|
201
201
|
|
|
202
202
|
```typescript
|
|
203
|
-
// ❌
|
|
203
|
+
// ❌ 금지: layout 없이 flat 구조
|
|
204
204
|
// app/auth/page.tsx
|
|
205
205
|
export default function AuthPage() {
|
|
206
206
|
return <div>Auth</div>
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
// ✅
|
|
209
|
+
// ✅ 필수: layout.tsx로 공통 UI 래핑
|
|
210
210
|
// app/(auth)/layout.tsx
|
|
211
211
|
export default function AuthLayout({
|
|
212
212
|
children,
|
|
@@ -235,28 +235,28 @@ export default function LoginPage() {
|
|
|
235
235
|
### 2. Server Actions Layer
|
|
236
236
|
|
|
237
237
|
```
|
|
238
|
-
app/_actions/ #
|
|
239
|
-
├── <action-name>.ts #
|
|
240
|
-
└── types.ts #
|
|
238
|
+
app/_actions/ # 글로벌 (재사용)
|
|
239
|
+
├── <action-name>.ts # 파일당 하나
|
|
240
|
+
└── types.ts # 공통 타입
|
|
241
241
|
|
|
242
|
-
app/<route>/_actions/ #
|
|
242
|
+
app/<route>/_actions/ # 페이지 전용
|
|
243
243
|
└── <action-name>.ts
|
|
244
244
|
```
|
|
245
245
|
|
|
246
|
-
> ⚠️
|
|
246
|
+
> ⚠️ **`app/_actions/index.ts` 생성 금지**
|
|
247
247
|
>
|
|
248
|
-
>
|
|
248
|
+
> `app/_actions/` 폴더에 `index.ts` (barrel export) 파일을 만들지 마세요.
|
|
249
249
|
>
|
|
250
|
-
>
|
|
251
|
-
> 1. **Tree Shaking
|
|
252
|
-
> 2. **Client
|
|
250
|
+
> **문제점:**
|
|
251
|
+
> 1. **Tree Shaking 실패** - 번들러가 사용하지 않는 함수도 포함
|
|
252
|
+
> 2. **Client 번들 오염** - `prisma` 등 서버 전용 라이브러리가 클라이언트에 포함되어 빌드 에러
|
|
253
253
|
>
|
|
254
254
|
> ```typescript
|
|
255
|
-
> // ❌
|
|
255
|
+
> // ❌ app/_actions/index.ts 만들지 말 것
|
|
256
256
|
> export * from './get-users'
|
|
257
|
-
> export * from './create-post' // prisma import →
|
|
257
|
+
> export * from './create-post' // prisma import → 클라이언트 빌드 실패
|
|
258
258
|
>
|
|
259
|
-
> // ✅
|
|
259
|
+
> // ✅ 개별 파일에서 직접 import
|
|
260
260
|
> import { getUsers } from '@/app/_actions/get-users'
|
|
261
261
|
> import { createPost } from '@/app/_actions/create-post'
|
|
262
262
|
> ```
|
|
@@ -265,9 +265,9 @@ app/<route>/_actions/ # page-specific
|
|
|
265
265
|
|
|
266
266
|
```
|
|
267
267
|
lib/<domain>/
|
|
268
|
-
├── index.ts #
|
|
269
|
-
├── schemas.ts # Zod
|
|
270
|
-
├── queries.ts # GET
|
|
268
|
+
├── index.ts # 진입점 (re-export)
|
|
269
|
+
├── schemas.ts # Zod 스키마
|
|
270
|
+
├── queries.ts # GET 요청
|
|
271
271
|
└── mutations.ts # POST/PUT/PATCH
|
|
272
272
|
```
|
|
273
273
|
|
|
@@ -298,23 +298,23 @@ if (process.env.NODE_ENV !== 'production') {
|
|
|
298
298
|
|
|
299
299
|
### Server Components vs Client Components
|
|
300
300
|
|
|
301
|
-
|
|
|
301
|
+
| 항목 | Server Components | Client Components |
|
|
302
302
|
|------|------------------|-------------------|
|
|
303
|
-
|
|
|
304
|
-
|
|
|
305
|
-
|
|
|
306
|
-
| **DB
|
|
307
|
-
|
|
|
308
|
-
|
|
|
309
|
-
|
|
|
303
|
+
| **기본값** | ✅ 기본 (명시 불필요) | ❌ `'use client'` 필수 |
|
|
304
|
+
| **실행 위치** | 서버 | 브라우저 |
|
|
305
|
+
| **데이터 페칭** | async/await 직접 사용 | TanStack Query/SWR |
|
|
306
|
+
| **DB 접근** | ✅ 가능 | ❌ 불가능 (Server Actions 사용) |
|
|
307
|
+
| **브라우저 API** | ❌ 불가능 | ✅ 가능 (window, localStorage 등) |
|
|
308
|
+
| **상태 관리** | ❌ 불가능 | ✅ 가능 (useState, useEffect 등) |
|
|
309
|
+
| **이벤트 핸들러** | ❌ 불가능 | ✅ 가능 (onClick, onChange 등) |
|
|
310
310
|
|
|
311
311
|
```typescript
|
|
312
|
-
// ✅ Server Component (
|
|
312
|
+
// ✅ Server Component (기본)
|
|
313
313
|
// app/users/page.tsx
|
|
314
314
|
import { prisma } from '@/lib/db/prisma'
|
|
315
315
|
|
|
316
316
|
export default async function UsersPage() {
|
|
317
|
-
//
|
|
317
|
+
// 서버에서 직접 DB 쿼리
|
|
318
318
|
const users = await prisma.user.findMany()
|
|
319
319
|
|
|
320
320
|
return (
|
|
@@ -345,13 +345,13 @@ export default function UserList() {
|
|
|
345
345
|
}
|
|
346
346
|
```
|
|
347
347
|
|
|
348
|
-
###
|
|
348
|
+
### 컴포넌트 구성 전략
|
|
349
349
|
|
|
350
350
|
```
|
|
351
351
|
Page (Server Component)
|
|
352
|
-
├─
|
|
352
|
+
├─ 데이터 페칭 (async/await)
|
|
353
353
|
└─ Interactive UI (Client Component)
|
|
354
|
-
└─
|
|
354
|
+
└─ 상태 관리, 이벤트 핸들러
|
|
355
355
|
```
|
|
356
356
|
|
|
357
357
|
</component_types>
|
|
@@ -364,35 +364,35 @@ Page (Server Component)
|
|
|
364
364
|
|
|
365
365
|
### Loading & Error Handling
|
|
366
366
|
|
|
367
|
-
|
|
|
368
|
-
|
|
369
|
-
| **loading.tsx** |
|
|
370
|
-
| **error.tsx** |
|
|
367
|
+
| 파일 | 용도 | 필수 |
|
|
368
|
+
|------|------|------|
|
|
369
|
+
| **loading.tsx** | 로딩 UI (Suspense boundary) | 선택 |
|
|
370
|
+
| **error.tsx** | 에러 UI (Error boundary) | ✅ |
|
|
371
371
|
| **not-found.tsx** | 404 UI | ✅ |
|
|
372
|
-
| **global-error.tsx** |
|
|
372
|
+
| **global-error.tsx** | 전역 에러 UI | 선택 |
|
|
373
373
|
|
|
374
374
|
```
|
|
375
375
|
app/
|
|
376
376
|
├── layout.tsx
|
|
377
|
-
├── loading.tsx #
|
|
378
|
-
├── error.tsx #
|
|
379
|
-
├── not-found.tsx #
|
|
380
|
-
├── global-error.tsx #
|
|
377
|
+
├── loading.tsx # 전역 로딩
|
|
378
|
+
├── error.tsx # 전역 에러
|
|
379
|
+
├── not-found.tsx # 전역 404
|
|
380
|
+
├── global-error.tsx # Root 에러 (layout.tsx 에러도 캐치)
|
|
381
381
|
└── users/
|
|
382
382
|
├── page.tsx
|
|
383
|
-
├── loading.tsx # /users
|
|
384
|
-
└── error.tsx # /users
|
|
383
|
+
├── loading.tsx # /users 로딩
|
|
384
|
+
└── error.tsx # /users 에러
|
|
385
385
|
```
|
|
386
386
|
|
|
387
|
-
###
|
|
387
|
+
### 코드 패턴
|
|
388
388
|
|
|
389
389
|
```typescript
|
|
390
|
-
// ✅ loading.tsx:
|
|
390
|
+
// ✅ loading.tsx: 로딩 UI
|
|
391
391
|
export default function Loading() {
|
|
392
392
|
return <div>Loading...</div>
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
-
// ✅ error.tsx:
|
|
395
|
+
// ✅ error.tsx: 에러 UI (Client Component 필수)
|
|
396
396
|
'use client'
|
|
397
397
|
|
|
398
398
|
export default function Error({
|
|
@@ -431,16 +431,16 @@ export default function NotFound() {
|
|
|
431
431
|
|
|
432
432
|
## Data Flow
|
|
433
433
|
|
|
434
|
-
### Query Flow (
|
|
434
|
+
### Query Flow (읽기)
|
|
435
435
|
|
|
436
436
|
```
|
|
437
437
|
Page (Server Component) → Prisma → Database
|
|
438
438
|
↓
|
|
439
|
-
|
|
439
|
+
자동 캐싱 (fetch cache)
|
|
440
440
|
```
|
|
441
441
|
|
|
442
442
|
```typescript
|
|
443
|
-
// ✅
|
|
443
|
+
// ✅ Server Component에서 직접 데이터 페칭
|
|
444
444
|
// app/users/page.tsx
|
|
445
445
|
import { prisma } from '@/lib/db/prisma'
|
|
446
446
|
|
|
@@ -456,16 +456,16 @@ export default async function UsersPage() {
|
|
|
456
456
|
)
|
|
457
457
|
}
|
|
458
458
|
|
|
459
|
-
// ✅ fetch with cache (
|
|
459
|
+
// ✅ fetch with cache (기본: 'force-cache')
|
|
460
460
|
async function getUsers() {
|
|
461
461
|
const res = await fetch('https://api.example.com/users', {
|
|
462
|
-
next: { revalidate: 3600 }, // 1
|
|
462
|
+
next: { revalidate: 3600 }, // 1시간 캐시
|
|
463
463
|
})
|
|
464
464
|
return res.json()
|
|
465
465
|
}
|
|
466
466
|
```
|
|
467
467
|
|
|
468
|
-
### Mutation Flow (
|
|
468
|
+
### Mutation Flow (쓰기)
|
|
469
469
|
|
|
470
470
|
```
|
|
471
471
|
Form (Client) → Server Action → Prisma → Database
|
|
@@ -488,7 +488,7 @@ const createUserSchema = z.object({
|
|
|
488
488
|
})
|
|
489
489
|
|
|
490
490
|
export async function createUser(formData: FormData) {
|
|
491
|
-
//
|
|
491
|
+
// 검증
|
|
492
492
|
const parsed = createUserSchema.safeParse({
|
|
493
493
|
name: formData.get('name'),
|
|
494
494
|
email: formData.get('email'),
|
|
@@ -498,18 +498,18 @@ export async function createUser(formData: FormData) {
|
|
|
498
498
|
return { error: parsed.error.errors }
|
|
499
499
|
}
|
|
500
500
|
|
|
501
|
-
// DB
|
|
501
|
+
// DB 저장
|
|
502
502
|
const user = await prisma.user.create({
|
|
503
503
|
data: parsed.data,
|
|
504
504
|
})
|
|
505
505
|
|
|
506
|
-
//
|
|
506
|
+
// 캐시 무효화
|
|
507
507
|
revalidatePath('/users')
|
|
508
508
|
|
|
509
509
|
return { success: true, user }
|
|
510
510
|
}
|
|
511
511
|
|
|
512
|
-
// ✅
|
|
512
|
+
// ✅ Client Component에서 사용
|
|
513
513
|
// app/users/_components/user-form.tsx
|
|
514
514
|
'use client'
|
|
515
515
|
|
|
@@ -544,13 +544,13 @@ export default function UserForm() {
|
|
|
544
544
|
|
|
545
545
|
## Server Actions (Advanced)
|
|
546
546
|
|
|
547
|
-
### Server Actions
|
|
547
|
+
### Server Actions 패턴
|
|
548
548
|
|
|
549
|
-
|
|
|
550
|
-
|
|
551
|
-
| **Form Actions** | `<form action={...}>` |
|
|
552
|
-
| **Programmatic** | `onClick={() => action()}` |
|
|
553
|
-
| **Progressive Enhancement** |
|
|
549
|
+
| 패턴 | 설명 | 사용 시점 |
|
|
550
|
+
|------|------|----------|
|
|
551
|
+
| **Form Actions** | `<form action={...}>` | 폼 제출 |
|
|
552
|
+
| **Programmatic** | `onClick={() => action()}` | 버튼 클릭 |
|
|
553
|
+
| **Progressive Enhancement** | JS 없이도 동작 | 접근성 중시 |
|
|
554
554
|
|
|
555
555
|
```typescript
|
|
556
556
|
// ✅ Form Action (Progressive Enhancement)
|
|
@@ -622,7 +622,7 @@ export default function UserList({ users }) {
|
|
|
622
622
|
}
|
|
623
623
|
```
|
|
624
624
|
|
|
625
|
-
###
|
|
625
|
+
### 인증 패턴
|
|
626
626
|
|
|
627
627
|
```typescript
|
|
628
628
|
// ✅ lib/auth/session.ts
|
|
@@ -631,11 +631,11 @@ import { cookies } from 'next/headers'
|
|
|
631
631
|
export async function getSession() {
|
|
632
632
|
const cookieStore = await cookies()
|
|
633
633
|
const session = cookieStore.get('session')
|
|
634
|
-
//
|
|
634
|
+
// 세션 검증 로직
|
|
635
635
|
return session
|
|
636
636
|
}
|
|
637
637
|
|
|
638
|
-
// ✅
|
|
638
|
+
// ✅ Server Action에서 인증 체크
|
|
639
639
|
// app/_actions/create-post.ts
|
|
640
640
|
'use server'
|
|
641
641
|
|
|
@@ -724,14 +724,14 @@ export default async function UserPage({ params }: Props) {
|
|
|
724
724
|
|
|
725
725
|
## Caching
|
|
726
726
|
|
|
727
|
-
### fetch()
|
|
727
|
+
### fetch() 캐싱
|
|
728
728
|
|
|
729
|
-
|
|
|
730
|
-
|
|
731
|
-
| `{ cache: 'force-cache' }` |
|
|
732
|
-
| `{ cache: 'no-store' }` |
|
|
733
|
-
| `{ next: { revalidate: 3600 } }` |
|
|
734
|
-
| `{ next: { tags: ['users'] } }` |
|
|
729
|
+
| 옵션 | 설명 |
|
|
730
|
+
|------|------|
|
|
731
|
+
| `{ cache: 'force-cache' }` | 기본값, 무기한 캐시 |
|
|
732
|
+
| `{ cache: 'no-store' }` | 캐시 사용 안 함 |
|
|
733
|
+
| `{ next: { revalidate: 3600 } }` | 3600초마다 재검증 |
|
|
734
|
+
| `{ next: { tags: ['users'] } }` | 태그 기반 무효화 |
|
|
735
735
|
|
|
736
736
|
```typescript
|
|
737
737
|
// ✅ fetch with cache
|
|
@@ -742,7 +742,7 @@ async function getUsers() {
|
|
|
742
742
|
return res.json()
|
|
743
743
|
}
|
|
744
744
|
|
|
745
|
-
// ✅
|
|
745
|
+
// ✅ 캐시 무효화
|
|
746
746
|
// app/_actions/create-user.ts
|
|
747
747
|
'use server'
|
|
748
748
|
|
|
@@ -751,18 +751,18 @@ import { revalidateTag, revalidatePath } from 'next/cache'
|
|
|
751
751
|
export async function createUser(data: any) {
|
|
752
752
|
// ...
|
|
753
753
|
|
|
754
|
-
//
|
|
754
|
+
// 태그 기반 무효화
|
|
755
755
|
revalidateTag('users')
|
|
756
756
|
|
|
757
|
-
//
|
|
757
|
+
// 경로 기반 무효화
|
|
758
758
|
revalidatePath('/users')
|
|
759
759
|
}
|
|
760
760
|
```
|
|
761
761
|
|
|
762
|
-
### unstable_cache (Prisma
|
|
762
|
+
### unstable_cache (Prisma 등)
|
|
763
763
|
|
|
764
764
|
```typescript
|
|
765
|
-
// ✅ Prisma
|
|
765
|
+
// ✅ Prisma 쿼리 캐싱
|
|
766
766
|
import { unstable_cache } from 'next/cache'
|
|
767
767
|
import { prisma } from '@/lib/db/prisma'
|
|
768
768
|
|