@su-record/vibe 2.3.0 → 2.3.2

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 (98) hide show
  1. package/.claude/settings.json +35 -35
  2. package/.claude/settings.local.json +24 -25
  3. package/.claude/vibe/constitution.md +184 -184
  4. package/.claude/vibe/rules/core/communication-guide.md +104 -104
  5. package/.claude/vibe/rules/core/development-philosophy.md +52 -52
  6. package/.claude/vibe/rules/core/quick-start.md +120 -120
  7. package/.claude/vibe/rules/languages/dart-flutter.md +509 -509
  8. package/.claude/vibe/rules/languages/go.md +396 -396
  9. package/.claude/vibe/rules/languages/java-spring.md +586 -586
  10. package/.claude/vibe/rules/languages/kotlin-android.md +491 -491
  11. package/.claude/vibe/rules/languages/python-django.md +371 -371
  12. package/.claude/vibe/rules/languages/python-fastapi.md +386 -386
  13. package/.claude/vibe/rules/languages/rust.md +425 -425
  14. package/.claude/vibe/rules/languages/swift-ios.md +516 -516
  15. package/.claude/vibe/rules/languages/typescript-nextjs.md +441 -441
  16. package/.claude/vibe/rules/languages/typescript-node.md +375 -375
  17. package/.claude/vibe/rules/languages/typescript-nuxt.md +521 -521
  18. package/.claude/vibe/rules/languages/typescript-react-native.md +446 -446
  19. package/.claude/vibe/rules/languages/typescript-react.md +525 -525
  20. package/.claude/vibe/rules/languages/typescript-vue.md +353 -353
  21. package/.claude/vibe/rules/quality/bdd-contract-testing.md +388 -388
  22. package/.claude/vibe/rules/quality/checklist.md +276 -276
  23. package/.claude/vibe/rules/quality/testing-strategy.md +437 -437
  24. package/.claude/vibe/rules/standards/anti-patterns.md +369 -369
  25. package/.claude/vibe/rules/standards/code-structure.md +291 -291
  26. package/.claude/vibe/rules/standards/complexity-metrics.md +312 -312
  27. package/.claude/vibe/rules/standards/naming-conventions.md +198 -198
  28. package/.claude/vibe/setup.sh +31 -31
  29. package/.claude/vibe/templates/constitution-template.md +184 -184
  30. package/.claude/vibe/templates/contract-backend-template.md +517 -517
  31. package/.claude/vibe/templates/contract-frontend-template.md +594 -594
  32. package/.claude/vibe/templates/feature-template.md +96 -96
  33. package/.claude/vibe/templates/spec-template.md +199 -199
  34. package/CLAUDE.md +345 -323
  35. package/LICENSE +21 -21
  36. package/README.md +744 -724
  37. package/agents/compounder.md +261 -261
  38. package/agents/diagrammer.md +178 -178
  39. package/agents/e2e-tester.md +266 -266
  40. package/agents/explorer.md +48 -48
  41. package/agents/implementer.md +53 -53
  42. package/agents/research/best-practices-agent.md +139 -139
  43. package/agents/research/codebase-patterns-agent.md +147 -147
  44. package/agents/research/framework-docs-agent.md +181 -181
  45. package/agents/research/security-advisory-agent.md +167 -167
  46. package/agents/review/architecture-reviewer.md +107 -107
  47. package/agents/review/complexity-reviewer.md +116 -116
  48. package/agents/review/data-integrity-reviewer.md +88 -88
  49. package/agents/review/git-history-reviewer.md +103 -103
  50. package/agents/review/performance-reviewer.md +86 -86
  51. package/agents/review/python-reviewer.md +152 -152
  52. package/agents/review/rails-reviewer.md +139 -139
  53. package/agents/review/react-reviewer.md +144 -144
  54. package/agents/review/security-reviewer.md +80 -80
  55. package/agents/review/simplicity-reviewer.md +140 -140
  56. package/agents/review/test-coverage-reviewer.md +116 -116
  57. package/agents/review/typescript-reviewer.md +127 -127
  58. package/agents/searcher.md +54 -54
  59. package/agents/simplifier.md +119 -119
  60. package/agents/tester.md +49 -49
  61. package/agents/ui-previewer.md +137 -137
  62. package/commands/vibe.analyze.md +245 -180
  63. package/commands/vibe.reason.md +223 -183
  64. package/commands/vibe.review.md +200 -136
  65. package/commands/vibe.run.md +838 -836
  66. package/commands/vibe.spec.md +419 -383
  67. package/commands/vibe.utils.md +101 -101
  68. package/commands/vibe.verify.md +282 -241
  69. package/dist/cli/index.js +385 -385
  70. package/dist/lib/MemoryManager.d.ts.map +1 -1
  71. package/dist/lib/MemoryManager.js +119 -114
  72. package/dist/lib/MemoryManager.js.map +1 -1
  73. package/dist/lib/PythonParser.js +108 -108
  74. package/dist/lib/gemini-mcp.js +15 -15
  75. package/dist/lib/gemini-oauth.js +35 -35
  76. package/dist/lib/gpt-mcp.js +17 -17
  77. package/dist/lib/gpt-oauth.js +44 -44
  78. package/dist/tools/analytics/getUsageAnalytics.js +12 -12
  79. package/dist/tools/index.d.ts +50 -0
  80. package/dist/tools/index.d.ts.map +1 -0
  81. package/dist/tools/index.js +61 -0
  82. package/dist/tools/index.js.map +1 -0
  83. package/dist/tools/memory/createMemoryTimeline.js +10 -10
  84. package/dist/tools/memory/getMemoryGraph.js +12 -12
  85. package/dist/tools/memory/getSessionContext.js +9 -9
  86. package/dist/tools/memory/linkMemories.js +14 -14
  87. package/dist/tools/memory/listMemories.js +4 -4
  88. package/dist/tools/memory/recallMemory.js +4 -4
  89. package/dist/tools/memory/saveMemory.js +4 -4
  90. package/dist/tools/memory/searchMemoriesAdvanced.js +22 -22
  91. package/dist/tools/planning/generatePrd.js +46 -46
  92. package/dist/tools/prompt/enhancePromptGemini.js +160 -160
  93. package/dist/tools/reasoning/applyReasoningFramework.js +56 -56
  94. package/dist/tools/semantic/analyzeDependencyGraph.js +12 -12
  95. package/hooks/hooks.json +121 -103
  96. package/package.json +73 -69
  97. package/skills/git-worktree.md +178 -178
  98. package/skills/priority-todos.md +236 -236
@@ -1,441 +1,441 @@
1
- # ⚡ TypeScript + Next.js 품질 규칙
2
-
3
- ## 핵심 원칙 (core + React에서 상속)
4
-
5
- ```markdown
6
- ✅ 단일 책임 (SRP)
7
- ✅ 중복 제거 (DRY)
8
- ✅ 재사용성
9
- ✅ 낮은 복잡도
10
- ✅ 함수 ≤ 30줄, JSX ≤ 50줄
11
- ✅ React 규칙 모두 적용
12
- ```
13
-
14
- ## Next.js 특화 규칙
15
-
16
- ### 1. App Router (Next.js 13+) 우선
17
-
18
- ```typescript
19
- // ✅ App Router 구조
20
- app/
21
- ├── layout.tsx # 루트 레이아웃
22
- ├── page.tsx # 홈 페이지
23
- ├── loading.tsx # 로딩 UI
24
- ├── error.tsx # 에러 UI
25
- ├── not-found.tsx # 404 페이지
26
- ├── users/
27
- │ ├── page.tsx # /users
28
- │ ├── [id]/
29
- │ │ └── page.tsx # /users/:id
30
- │ └── loading.tsx # /users 로딩
31
- └── api/
32
- └── users/
33
- └── route.ts # API Route
34
-
35
- // ✅ 서버 컴포넌트 (기본)
36
- export default async function UsersPage() {
37
- // 서버에서 데이터 페칭
38
- const users = await getUsers();
39
-
40
- return (
41
- <div>
42
- <h1>Users</h1>
43
- <UserList users={users} />
44
- </div>
45
- );
46
- }
47
-
48
- // ✅ 클라이언트 컴포넌트 (필요 시에만)
49
- 'use client';
50
-
51
- import { useState } from 'react';
52
-
53
- export function InteractiveButton() {
54
- const [count, setCount] = useState(0);
55
-
56
- return <button onClick={() => setCount(count + 1)}>{count}</button>;
57
- }
58
- ```
59
-
60
- ### 2. 서버 컴포넌트 vs 클라이언트 컴포넌트
61
-
62
- ```typescript
63
- // ✅ 서버 컴포넌트 (권장)
64
- // - 데이터 페칭
65
- // - 환경 변수 접근
66
- // - DB 직접 접근
67
- // - 민감한 정보 처리
68
-
69
- async function UserProfile({ userId }: { userId: string }) {
70
- // 서버에서만 실행 (API 키 노출 안 됨)
71
- const user = await db.user.findUnique({
72
- where: { id: userId },
73
- });
74
-
75
- return <div>{user.name}</div>;
76
- }
77
-
78
- // ✅ 클라이언트 컴포넌트 (필요 시만)
79
- // - useState, useEffect 사용
80
- // - 이벤트 핸들러
81
- // - 브라우저 API
82
- // - 서드파티 라이브러리 (대부분)
83
-
84
- 'use client';
85
-
86
- function SearchBar() {
87
- const [query, setQuery] = useState('');
88
-
89
- return <input value={query} onChange={e => setQuery(e.target.value)} />;
90
- }
91
- ```
92
-
93
- ### 3. Data Fetching 패턴
94
-
95
- ```typescript
96
- // ✅ 서버 컴포넌트에서 직접 fetch
97
- async function PostsPage() {
98
- // 자동 캐싱, 재검증
99
- const posts = await fetch('https://api.example.com/posts', {
100
- next: { revalidate: 60 }, // 60초 캐싱
101
- }).then(res => res.json());
102
-
103
- return <PostList posts={posts} />;
104
- }
105
-
106
- // ✅ 병렬 데이터 페칭
107
- async function UserDashboard({ userId }: { userId: string }) {
108
- const [user, posts, comments] = await Promise.all([
109
- getUser(userId),
110
- getUserPosts(userId),
111
- getUserComments(userId),
112
- ]);
113
-
114
- return (
115
- <div>
116
- <UserCard user={user} />
117
- <PostList posts={posts} />
118
- <CommentList comments={comments} />
119
- </div>
120
- );
121
- }
122
-
123
- // ✅ 순차적 데이터 페칭 (의존 관계)
124
- async function UserWithPosts({ username }: { username: string }) {
125
- // 1. 사용자 조회
126
- const user = await getUserByUsername(username);
127
-
128
- // 2. 사용자 ID로 게시물 조회
129
- const posts = await getUserPosts(user.id);
130
-
131
- return (
132
- <div>
133
- <UserCard user={user} />
134
- <PostList posts={posts} />
135
- </div>
136
- );
137
- }
138
- ```
139
-
140
- ### 4. API Routes (Route Handlers)
141
-
142
- ```typescript
143
- // app/api/users/route.ts
144
- import { NextRequest, NextResponse } from 'next/server';
145
- import { z } from 'zod';
146
-
147
- const createUserSchema = z.object({
148
- email: z.string().email(),
149
- name: z.string().min(1),
150
- });
151
-
152
- // ✅ GET /api/users
153
- export async function GET(request: NextRequest) {
154
- try {
155
- const users = await db.user.findMany();
156
- return NextResponse.json(users);
157
- } catch (error) {
158
- return NextResponse.json(
159
- { error: 'Failed to fetch users' },
160
- { status: 500 }
161
- );
162
- }
163
- }
164
-
165
- // ✅ POST /api/users
166
- export async function POST(request: NextRequest) {
167
- try {
168
- const body = await request.json();
169
- const data = createUserSchema.parse(body);
170
-
171
- const user = await db.user.create({ data });
172
-
173
- return NextResponse.json(user, { status: 201 });
174
- } catch (error) {
175
- if (error instanceof z.ZodError) {
176
- return NextResponse.json({ error: error.errors }, { status: 400 });
177
- }
178
- return NextResponse.json(
179
- { error: 'Failed to create user' },
180
- { status: 500 }
181
- );
182
- }
183
- }
184
-
185
- // app/api/users/[id]/route.ts
186
- // ✅ GET /api/users/:id
187
- export async function GET(
188
- request: NextRequest,
189
- { params }: { params: { id: string } }
190
- ) {
191
- const user = await db.user.findUnique({
192
- where: { id: params.id },
193
- });
194
-
195
- if (!user) {
196
- return NextResponse.json({ error: 'User not found' }, { status: 404 });
197
- }
198
-
199
- return NextResponse.json(user);
200
- }
201
- ```
202
-
203
- ### 5. Metadata & SEO
204
-
205
- ```typescript
206
- // ✅ 정적 메타데이터
207
- import { Metadata } from 'next';
208
-
209
- export const metadata: Metadata = {
210
- title: 'My App',
211
- description: 'My awesome app',
212
- openGraph: {
213
- title: 'My App',
214
- description: 'My awesome app',
215
- images: ['/og-image.png'],
216
- },
217
- };
218
-
219
- // ✅ 동적 메타데이터
220
- export async function generateMetadata({
221
- params,
222
- }: {
223
- params: { id: string };
224
- }): Promise<Metadata> {
225
- const user = await getUser(params.id);
226
-
227
- return {
228
- title: user.name,
229
- description: user.bio,
230
- openGraph: {
231
- title: user.name,
232
- images: [user.avatar],
233
- },
234
- };
235
- }
236
- ```
237
-
238
- ### 6. Streaming & Suspense
239
-
240
- ```typescript
241
- // ✅ Streaming으로 빠른 초기 렌더링
242
- import { Suspense } from 'react';
243
-
244
- export default function Dashboard() {
245
- return (
246
- <div>
247
- <h1>Dashboard</h1>
248
-
249
- {/* 빠른 컴포넌트 먼저 렌더 */}
250
- <QuickStats />
251
-
252
- {/* 느린 컴포넌트 나중에 스트리밍 */}
253
- <Suspense fallback={<ChartSkeleton />}>
254
- <SlowChart />
255
- </Suspense>
256
-
257
- <Suspense fallback={<TableSkeleton />}>
258
- <SlowTable />
259
- </Suspense>
260
- </div>
261
- );
262
- }
263
-
264
- async function SlowChart() {
265
- const data = await fetchSlowData();
266
- return <Chart data={data} />;
267
- }
268
- ```
269
-
270
- ### 7. Server Actions
271
-
272
- ```typescript
273
- // ✅ Server Action (서버에서만 실행)
274
- 'use server';
275
-
276
- import { revalidatePath } from 'next/cache';
277
-
278
- export async function createUser(formData: FormData) {
279
- const email = formData.get('email') as string;
280
- const name = formData.get('name') as string;
281
-
282
- // 서버에서 직접 DB 접근
283
- const user = await db.user.create({
284
- data: { email, name },
285
- });
286
-
287
- // 캐시 재검증
288
- revalidatePath('/users');
289
-
290
- return user;
291
- }
292
-
293
- // ✅ 클라이언트에서 사용
294
- 'use client';
295
-
296
- import { createUser } from './actions';
297
-
298
- export function CreateUserForm() {
299
- return (
300
- <form action={createUser}>
301
- <input name="email" type="email" required />
302
- <input name="name" required />
303
- <button type="submit">Create</button>
304
- </form>
305
- );
306
- }
307
- ```
308
-
309
- ### 8. Middleware
310
-
311
- ```typescript
312
- // middleware.ts (루트)
313
- import { NextResponse } from 'next/server';
314
- import type { NextRequest } from 'next/server';
315
-
316
- export function middleware(request: NextRequest) {
317
- // 인증 체크
318
- const token = request.cookies.get('token')?.value;
319
-
320
- if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
321
- return NextResponse.redirect(new URL('/login', request.url));
322
- }
323
-
324
- // 헤더 추가
325
- const response = NextResponse.next();
326
- response.headers.set('X-Custom-Header', 'value');
327
-
328
- return response;
329
- }
330
-
331
- export const config = {
332
- matcher: ['/dashboard/:path*', '/api/:path*'],
333
- };
334
- ```
335
-
336
- ### 9. 환경 변수
337
-
338
- ```typescript
339
- // ✅ 서버 전용 환경 변수
340
- const dbUrl = process.env.DATABASE_URL; // 서버 컴포넌트에서만
341
- const apiKey = process.env.API_SECRET_KEY;
342
-
343
- // ✅ 클라이언트 노출 환경 변수 (NEXT_PUBLIC_ 접두사)
344
- const publicUrl = process.env.NEXT_PUBLIC_API_URL;
345
-
346
- // .env.local
347
- DATABASE_URL=postgresql://...
348
- API_SECRET_KEY=secret123
349
- NEXT_PUBLIC_API_URL=https://api.example.com
350
- ```
351
-
352
- ### 10. 이미지 최적화
353
-
354
- ```typescript
355
- import Image from 'next/image';
356
-
357
- // ✅ Next.js Image 컴포넌트 (자동 최적화)
358
- export function UserAvatar({ user }: { user: User }) {
359
- return (
360
- <Image
361
- src={user.avatar}
362
- alt={user.name}
363
- width={100}
364
- height={100}
365
- priority // LCP 이미지는 priority
366
- />
367
- );
368
- }
369
-
370
- // ✅ 외부 이미지 (next.config.js 설정 필요)
371
- // next.config.js
372
- module.exports = {
373
- images: {
374
- domains: ['example.com', 'cdn.example.com'],
375
- },
376
- };
377
- ```
378
-
379
- ## 안티패턴
380
-
381
- ```typescript
382
- // ❌ 클라이언트 컴포넌트에서 서버 전용 코드
383
- 'use client';
384
-
385
- function BadComponent() {
386
- const data = await db.user.findMany(); // ❌ 클라이언트에서 DB 접근 불가
387
- }
388
-
389
- // ❌ 서버 컴포넌트에서 브라우저 API
390
- async function BadServerComponent() {
391
- const width = window.innerWidth; // ❌ window는 브라우저에만 존재
392
- }
393
-
394
- // ❌ API Route에서 다른 API Route 호출
395
- export async function GET() {
396
- const response = await fetch('http://localhost:3000/api/users'); // ❌
397
- // 대신 직접 DB 함수 호출
398
- const users = await getUsers(); // ✅
399
- }
400
-
401
- // ❌ 환경 변수 노출
402
- 'use client';
403
-
404
- function BadClient() {
405
- const apiKey = process.env.API_SECRET_KEY; // ❌ undefined (클라이언트에서)
406
- }
407
- ```
408
-
409
- ## 성능 최적화
410
-
411
- ```typescript
412
- // ✅ Static Generation (SSG)
413
- export async function generateStaticParams() {
414
- const posts = await getPosts();
415
- return posts.map(post => ({ id: post.id }));
416
- }
417
-
418
- // ✅ Incremental Static Regeneration (ISR)
419
- export const revalidate = 60; // 60초마다 재생성
420
-
421
- // ✅ Dynamic Rendering (SSR)
422
- export const dynamic = 'force-dynamic';
423
-
424
- // ✅ Partial Prerendering (실험적)
425
- export const experimental_ppr = true;
426
- ```
427
-
428
- ## 체크리스트
429
-
430
- Next.js 코드 작성 시:
431
-
432
- - [ ] App Router 사용 (Pages Router 지양)
433
- - [ ] 서버 컴포넌트 우선 (클라이언트 최소화)
434
- - [ ] API Route 대신 Server Action 고려
435
- - [ ] 메타데이터 정의 (SEO)
436
- - [ ] Suspense로 Streaming
437
- - [ ] Next.js Image 컴포넌트 사용
438
- - [ ] 환경 변수 올바르게 사용
439
- - [ ] Middleware로 인증/권한 체크
440
- - [ ] 캐싱 전략 설정 (revalidate)
441
- - [ ] TypeScript 엄격 모드
1
+ # ⚡ TypeScript + Next.js 품질 규칙
2
+
3
+ ## 핵심 원칙 (core + React에서 상속)
4
+
5
+ ```markdown
6
+ ✅ 단일 책임 (SRP)
7
+ ✅ 중복 제거 (DRY)
8
+ ✅ 재사용성
9
+ ✅ 낮은 복잡도
10
+ ✅ 함수 ≤ 30줄, JSX ≤ 50줄
11
+ ✅ React 규칙 모두 적용
12
+ ```
13
+
14
+ ## Next.js 특화 규칙
15
+
16
+ ### 1. App Router (Next.js 13+) 우선
17
+
18
+ ```typescript
19
+ // ✅ App Router 구조
20
+ app/
21
+ ├── layout.tsx # 루트 레이아웃
22
+ ├── page.tsx # 홈 페이지
23
+ ├── loading.tsx # 로딩 UI
24
+ ├── error.tsx # 에러 UI
25
+ ├── not-found.tsx # 404 페이지
26
+ ├── users/
27
+ │ ├── page.tsx # /users
28
+ │ ├── [id]/
29
+ │ │ └── page.tsx # /users/:id
30
+ │ └── loading.tsx # /users 로딩
31
+ └── api/
32
+ └── users/
33
+ └── route.ts # API Route
34
+
35
+ // ✅ 서버 컴포넌트 (기본)
36
+ export default async function UsersPage() {
37
+ // 서버에서 데이터 페칭
38
+ const users = await getUsers();
39
+
40
+ return (
41
+ <div>
42
+ <h1>Users</h1>
43
+ <UserList users={users} />
44
+ </div>
45
+ );
46
+ }
47
+
48
+ // ✅ 클라이언트 컴포넌트 (필요 시에만)
49
+ 'use client';
50
+
51
+ import { useState } from 'react';
52
+
53
+ export function InteractiveButton() {
54
+ const [count, setCount] = useState(0);
55
+
56
+ return <button onClick={() => setCount(count + 1)}>{count}</button>;
57
+ }
58
+ ```
59
+
60
+ ### 2. 서버 컴포넌트 vs 클라이언트 컴포넌트
61
+
62
+ ```typescript
63
+ // ✅ 서버 컴포넌트 (권장)
64
+ // - 데이터 페칭
65
+ // - 환경 변수 접근
66
+ // - DB 직접 접근
67
+ // - 민감한 정보 처리
68
+
69
+ async function UserProfile({ userId }: { userId: string }) {
70
+ // 서버에서만 실행 (API 키 노출 안 됨)
71
+ const user = await db.user.findUnique({
72
+ where: { id: userId },
73
+ });
74
+
75
+ return <div>{user.name}</div>;
76
+ }
77
+
78
+ // ✅ 클라이언트 컴포넌트 (필요 시만)
79
+ // - useState, useEffect 사용
80
+ // - 이벤트 핸들러
81
+ // - 브라우저 API
82
+ // - 서드파티 라이브러리 (대부분)
83
+
84
+ 'use client';
85
+
86
+ function SearchBar() {
87
+ const [query, setQuery] = useState('');
88
+
89
+ return <input value={query} onChange={e => setQuery(e.target.value)} />;
90
+ }
91
+ ```
92
+
93
+ ### 3. Data Fetching 패턴
94
+
95
+ ```typescript
96
+ // ✅ 서버 컴포넌트에서 직접 fetch
97
+ async function PostsPage() {
98
+ // 자동 캐싱, 재검증
99
+ const posts = await fetch('https://api.example.com/posts', {
100
+ next: { revalidate: 60 }, // 60초 캐싱
101
+ }).then(res => res.json());
102
+
103
+ return <PostList posts={posts} />;
104
+ }
105
+
106
+ // ✅ 병렬 데이터 페칭
107
+ async function UserDashboard({ userId }: { userId: string }) {
108
+ const [user, posts, comments] = await Promise.all([
109
+ getUser(userId),
110
+ getUserPosts(userId),
111
+ getUserComments(userId),
112
+ ]);
113
+
114
+ return (
115
+ <div>
116
+ <UserCard user={user} />
117
+ <PostList posts={posts} />
118
+ <CommentList comments={comments} />
119
+ </div>
120
+ );
121
+ }
122
+
123
+ // ✅ 순차적 데이터 페칭 (의존 관계)
124
+ async function UserWithPosts({ username }: { username: string }) {
125
+ // 1. 사용자 조회
126
+ const user = await getUserByUsername(username);
127
+
128
+ // 2. 사용자 ID로 게시물 조회
129
+ const posts = await getUserPosts(user.id);
130
+
131
+ return (
132
+ <div>
133
+ <UserCard user={user} />
134
+ <PostList posts={posts} />
135
+ </div>
136
+ );
137
+ }
138
+ ```
139
+
140
+ ### 4. API Routes (Route Handlers)
141
+
142
+ ```typescript
143
+ // app/api/users/route.ts
144
+ import { NextRequest, NextResponse } from 'next/server';
145
+ import { z } from 'zod';
146
+
147
+ const createUserSchema = z.object({
148
+ email: z.string().email(),
149
+ name: z.string().min(1),
150
+ });
151
+
152
+ // ✅ GET /api/users
153
+ export async function GET(request: NextRequest) {
154
+ try {
155
+ const users = await db.user.findMany();
156
+ return NextResponse.json(users);
157
+ } catch (error) {
158
+ return NextResponse.json(
159
+ { error: 'Failed to fetch users' },
160
+ { status: 500 }
161
+ );
162
+ }
163
+ }
164
+
165
+ // ✅ POST /api/users
166
+ export async function POST(request: NextRequest) {
167
+ try {
168
+ const body = await request.json();
169
+ const data = createUserSchema.parse(body);
170
+
171
+ const user = await db.user.create({ data });
172
+
173
+ return NextResponse.json(user, { status: 201 });
174
+ } catch (error) {
175
+ if (error instanceof z.ZodError) {
176
+ return NextResponse.json({ error: error.errors }, { status: 400 });
177
+ }
178
+ return NextResponse.json(
179
+ { error: 'Failed to create user' },
180
+ { status: 500 }
181
+ );
182
+ }
183
+ }
184
+
185
+ // app/api/users/[id]/route.ts
186
+ // ✅ GET /api/users/:id
187
+ export async function GET(
188
+ request: NextRequest,
189
+ { params }: { params: { id: string } }
190
+ ) {
191
+ const user = await db.user.findUnique({
192
+ where: { id: params.id },
193
+ });
194
+
195
+ if (!user) {
196
+ return NextResponse.json({ error: 'User not found' }, { status: 404 });
197
+ }
198
+
199
+ return NextResponse.json(user);
200
+ }
201
+ ```
202
+
203
+ ### 5. Metadata & SEO
204
+
205
+ ```typescript
206
+ // ✅ 정적 메타데이터
207
+ import { Metadata } from 'next';
208
+
209
+ export const metadata: Metadata = {
210
+ title: 'My App',
211
+ description: 'My awesome app',
212
+ openGraph: {
213
+ title: 'My App',
214
+ description: 'My awesome app',
215
+ images: ['/og-image.png'],
216
+ },
217
+ };
218
+
219
+ // ✅ 동적 메타데이터
220
+ export async function generateMetadata({
221
+ params,
222
+ }: {
223
+ params: { id: string };
224
+ }): Promise<Metadata> {
225
+ const user = await getUser(params.id);
226
+
227
+ return {
228
+ title: user.name,
229
+ description: user.bio,
230
+ openGraph: {
231
+ title: user.name,
232
+ images: [user.avatar],
233
+ },
234
+ };
235
+ }
236
+ ```
237
+
238
+ ### 6. Streaming & Suspense
239
+
240
+ ```typescript
241
+ // ✅ Streaming으로 빠른 초기 렌더링
242
+ import { Suspense } from 'react';
243
+
244
+ export default function Dashboard() {
245
+ return (
246
+ <div>
247
+ <h1>Dashboard</h1>
248
+
249
+ {/* 빠른 컴포넌트 먼저 렌더 */}
250
+ <QuickStats />
251
+
252
+ {/* 느린 컴포넌트 나중에 스트리밍 */}
253
+ <Suspense fallback={<ChartSkeleton />}>
254
+ <SlowChart />
255
+ </Suspense>
256
+
257
+ <Suspense fallback={<TableSkeleton />}>
258
+ <SlowTable />
259
+ </Suspense>
260
+ </div>
261
+ );
262
+ }
263
+
264
+ async function SlowChart() {
265
+ const data = await fetchSlowData();
266
+ return <Chart data={data} />;
267
+ }
268
+ ```
269
+
270
+ ### 7. Server Actions
271
+
272
+ ```typescript
273
+ // ✅ Server Action (서버에서만 실행)
274
+ 'use server';
275
+
276
+ import { revalidatePath } from 'next/cache';
277
+
278
+ export async function createUser(formData: FormData) {
279
+ const email = formData.get('email') as string;
280
+ const name = formData.get('name') as string;
281
+
282
+ // 서버에서 직접 DB 접근
283
+ const user = await db.user.create({
284
+ data: { email, name },
285
+ });
286
+
287
+ // 캐시 재검증
288
+ revalidatePath('/users');
289
+
290
+ return user;
291
+ }
292
+
293
+ // ✅ 클라이언트에서 사용
294
+ 'use client';
295
+
296
+ import { createUser } from './actions';
297
+
298
+ export function CreateUserForm() {
299
+ return (
300
+ <form action={createUser}>
301
+ <input name="email" type="email" required />
302
+ <input name="name" required />
303
+ <button type="submit">Create</button>
304
+ </form>
305
+ );
306
+ }
307
+ ```
308
+
309
+ ### 8. Middleware
310
+
311
+ ```typescript
312
+ // middleware.ts (루트)
313
+ import { NextResponse } from 'next/server';
314
+ import type { NextRequest } from 'next/server';
315
+
316
+ export function middleware(request: NextRequest) {
317
+ // 인증 체크
318
+ const token = request.cookies.get('token')?.value;
319
+
320
+ if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
321
+ return NextResponse.redirect(new URL('/login', request.url));
322
+ }
323
+
324
+ // 헤더 추가
325
+ const response = NextResponse.next();
326
+ response.headers.set('X-Custom-Header', 'value');
327
+
328
+ return response;
329
+ }
330
+
331
+ export const config = {
332
+ matcher: ['/dashboard/:path*', '/api/:path*'],
333
+ };
334
+ ```
335
+
336
+ ### 9. 환경 변수
337
+
338
+ ```typescript
339
+ // ✅ 서버 전용 환경 변수
340
+ const dbUrl = process.env.DATABASE_URL; // 서버 컴포넌트에서만
341
+ const apiKey = process.env.API_SECRET_KEY;
342
+
343
+ // ✅ 클라이언트 노출 환경 변수 (NEXT_PUBLIC_ 접두사)
344
+ const publicUrl = process.env.NEXT_PUBLIC_API_URL;
345
+
346
+ // .env.local
347
+ DATABASE_URL=postgresql://...
348
+ API_SECRET_KEY=secret123
349
+ NEXT_PUBLIC_API_URL=https://api.example.com
350
+ ```
351
+
352
+ ### 10. 이미지 최적화
353
+
354
+ ```typescript
355
+ import Image from 'next/image';
356
+
357
+ // ✅ Next.js Image 컴포넌트 (자동 최적화)
358
+ export function UserAvatar({ user }: { user: User }) {
359
+ return (
360
+ <Image
361
+ src={user.avatar}
362
+ alt={user.name}
363
+ width={100}
364
+ height={100}
365
+ priority // LCP 이미지는 priority
366
+ />
367
+ );
368
+ }
369
+
370
+ // ✅ 외부 이미지 (next.config.js 설정 필요)
371
+ // next.config.js
372
+ module.exports = {
373
+ images: {
374
+ domains: ['example.com', 'cdn.example.com'],
375
+ },
376
+ };
377
+ ```
378
+
379
+ ## 안티패턴
380
+
381
+ ```typescript
382
+ // ❌ 클라이언트 컴포넌트에서 서버 전용 코드
383
+ 'use client';
384
+
385
+ function BadComponent() {
386
+ const data = await db.user.findMany(); // ❌ 클라이언트에서 DB 접근 불가
387
+ }
388
+
389
+ // ❌ 서버 컴포넌트에서 브라우저 API
390
+ async function BadServerComponent() {
391
+ const width = window.innerWidth; // ❌ window는 브라우저에만 존재
392
+ }
393
+
394
+ // ❌ API Route에서 다른 API Route 호출
395
+ export async function GET() {
396
+ const response = await fetch('http://localhost:3000/api/users'); // ❌
397
+ // 대신 직접 DB 함수 호출
398
+ const users = await getUsers(); // ✅
399
+ }
400
+
401
+ // ❌ 환경 변수 노출
402
+ 'use client';
403
+
404
+ function BadClient() {
405
+ const apiKey = process.env.API_SECRET_KEY; // ❌ undefined (클라이언트에서)
406
+ }
407
+ ```
408
+
409
+ ## 성능 최적화
410
+
411
+ ```typescript
412
+ // ✅ Static Generation (SSG)
413
+ export async function generateStaticParams() {
414
+ const posts = await getPosts();
415
+ return posts.map(post => ({ id: post.id }));
416
+ }
417
+
418
+ // ✅ Incremental Static Regeneration (ISR)
419
+ export const revalidate = 60; // 60초마다 재생성
420
+
421
+ // ✅ Dynamic Rendering (SSR)
422
+ export const dynamic = 'force-dynamic';
423
+
424
+ // ✅ Partial Prerendering (실험적)
425
+ export const experimental_ppr = true;
426
+ ```
427
+
428
+ ## 체크리스트
429
+
430
+ Next.js 코드 작성 시:
431
+
432
+ - [ ] App Router 사용 (Pages Router 지양)
433
+ - [ ] 서버 컴포넌트 우선 (클라이언트 최소화)
434
+ - [ ] API Route 대신 Server Action 고려
435
+ - [ ] 메타데이터 정의 (SEO)
436
+ - [ ] Suspense로 Streaming
437
+ - [ ] Next.js Image 컴포넌트 사용
438
+ - [ ] 환경 변수 올바르게 사용
439
+ - [ ] Middleware로 인증/권한 체크
440
+ - [ ] 캐싱 전략 설정 (revalidate)
441
+ - [ ] TypeScript 엄격 모드