@wondev/dotenv-example 1.0.0

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/.claude/README.md +60 -0
  2. package/.claude/commands/business_logic.md +143 -0
  3. package/.claude/commands/generate-prd.md +175 -0
  4. package/.claude/commands/gotobackend.md +569 -0
  5. package/.claude/commands/playwrightMCP_install.md +113 -0
  6. package/.claude/commands/setting_dev.md +731 -0
  7. package/.claude/commands/tech-lead.md +404 -0
  8. package/.claude/commands/user-flow.md +839 -0
  9. package/.claude/settings.local.json +9 -0
  10. package/.cursor/README.md +10 -0
  11. package/.cursor/mcp.json +31 -0
  12. package/.cursor/rules/common/cursor-rules.mdc +53 -0
  13. package/.cursor/rules/common/git-convention.mdc +86 -0
  14. package/.cursor/rules/common/self-improve.mdc +72 -0
  15. package/.cursor/rules/common/tdd.mdc +81 -0
  16. package/.cursor/rules/common/vibe-coding.mdc +114 -0
  17. package/.cursor/rules/supabase/supabase-bootstrap-auth.mdc +236 -0
  18. package/.cursor/rules/supabase/supabase-create-db-functions.mdc +136 -0
  19. package/.cursor/rules/supabase/supabase-create-migration.mdc +50 -0
  20. package/.cursor/rules/supabase/supabase-create-rls-policies.mdc +248 -0
  21. package/.cursor/rules/supabase/supabase-declarative-database-schema.mdc +78 -0
  22. package/.cursor/rules/supabase/supabase-postgres-sql-style-guide.mdc +133 -0
  23. package/.cursor/rules/supabase/supabase-writing-edge-functions.mdc +105 -0
  24. package/.cursor/rules/web/design-rules.mdc +381 -0
  25. package/.cursor/rules/web/nextjs-convention.mdc +237 -0
  26. package/.cursor/rules/web/playwright-test-guide.mdc +176 -0
  27. package/.cursor/rules/web/toss-frontend.mdc +695 -0
  28. package/.env +4 -0
  29. package/CLAUDE.md +40 -0
  30. package/README.md +7 -0
  31. package/bin/index.js +66 -0
  32. package/package.json +32 -0
  33. package/prompts/20250926_175606.md +3 -0
  34. package/prompts/20250926_180205.md +148 -0
@@ -0,0 +1,731 @@
1
+ ### 즉시 시작 가능한 개발 환경 설정
2
+ ```bash
3
+ # Next.js 15 프로젝트 생성
4
+ pnpm create next-app@latest . --typescript --tailwind --app
5
+
6
+ # shadcn/ui 초기화
7
+ pnpx shadcn@latest init
8
+
9
+ # 필수 패키지 설치
10
+ pnpm add @tanstack/react-query jotai lucide-react zod
11
+ pnpm add -D vitest @vitejs/plugin-react jsdom @testing-library/react vite-tsconfig-paths
12
+
13
+ # 폴더 구조 생성
14
+ mkdir -p src/{actions,lib/{db/local/{repositories,models,utils},auth,},hooks,states,types}
15
+ ```
16
+
17
+ # Next.js 웹 서비스 기술 스택 & 폴더 구조 가이드 (로컬 스토리지 기반)
18
+
19
+ ## 🚀 기술 스택
20
+
21
+ ### 프론트엔드
22
+ - **Next.js 15** - React 19 기반 풀스택 프레임워크
23
+ - **Tailwind CSS v3** - 유틸리티 퍼스트 CSS 프레임워크
24
+ - **shadcn/ui** - Tailwind 기반 고품질 컴포넌트 라이브러리
25
+ - **Lucide React** - 가벼운 아이콘 라이브러리
26
+
27
+ ### 상태 관리 & 데이터
28
+ - **Jotai** - 원자 단위 전역 상태 관리
29
+ - **TanStack Query (React Query)** - 서버 상태 관리 및 데이터 페칭
30
+ - **Local Storage** - 로컬 데이터 저장소
31
+
32
+ ### 개발 환경
33
+ - **TypeScript** - 타입 안전성
34
+ - **Vitest** - 빠른 테스트 러너
35
+ - **pnpm** - 효율적인 패키지 매니저
36
+ - **Vercel** - 최적화된 배포 플랫폼
37
+
38
+ ## 📁 프로젝트 폴더 구조
39
+
40
+ ```
41
+ /
42
+ ├── .env.local # 환경 변수
43
+ ├── .env.example # 환경 변수 예시
44
+ ├── .gitignore
45
+ ├── package.json
46
+ ├── pnpm-lock.yaml
47
+ ├── next.config.js
48
+ ├── tailwind.config.ts
49
+ ├── tsconfig.json
50
+ ├── vitest.config.mts
51
+ ├── README.md
52
+
53
+ ├── public/ # 정적 파일
54
+ │ ├── favicon.ico
55
+ │ ├── images/
56
+ │ └── icons/
57
+
58
+ ├── src/
59
+ │ ├── app/ # Next.js App Router (라우팅 전용)
60
+ │ │ ├── globals.css
61
+ │ │ ├── layout.tsx
62
+ │ │ ├── page.tsx
63
+ │ │ ├── loading.tsx
64
+ │ │ ├── error.tsx
65
+ │ │ ├── not-found.tsx
66
+ │ │ │
67
+ │ │ ├── (auth)/ # 라우트 그룹
68
+ │ │ │ ├── login/
69
+ │ │ │ │ └── page.tsx
70
+ │ │ │ └── register/
71
+ │ │ │ └── page.tsx
72
+ │ │ │
73
+ │ │ ├── dashboard/
74
+ │ │ │ ├── layout.tsx
75
+ │ │ │ ├── page.tsx
76
+ │ │ │ └── settings/
77
+ │ │ │ └── page.tsx
78
+ │ │ │
79
+ │ │ └── api/ # API 라우트 (최소한으로 사용)
80
+ │ │ └── webhook/
81
+ │ │ └── route.ts
82
+ │ │
83
+ │ ├── actions/ # Server Actions (API 대신 우선 사용)
84
+ │ │ ├── auth-actions.ts
85
+ │ │ ├── user-actions.ts
86
+ │ │ └── post-actions.ts
87
+ │ │
88
+ │ ├── components/ # 재사용 가능한 컴포넌트
89
+ │ │ ├── ui/ # shadcn/ui 컴포넌트
90
+ │ │ │ ├── button.tsx
91
+ │ │ │ ├── input.tsx
92
+ │ │ │ ├── card.tsx
93
+ │ │ │ └── dialog.tsx
94
+ │ │ │
95
+ │ │ ├── forms/ # 폼 관련 컴포넌트
96
+ │ │ │ ├── login-form.tsx
97
+ │ │ │ ├── register-form.tsx
98
+ │ │ │ └── contact-form.tsx
99
+ │ │ │
100
+ │ │ ├── layout/ # 레이아웃 컴포넌트
101
+ │ │ │ ├── header.tsx
102
+ │ │ │ ├── footer.tsx
103
+ │ │ │ ├── sidebar.tsx
104
+ │ │ │ └── navigation.tsx
105
+ │ │ │
106
+ │ │ ├── features/ # 기능별 컴포넌트
107
+ │ │ │ ├── auth/
108
+ │ │ │ │ ├── auth-provider.tsx
109
+ │ │ │ │ └── protected-route.tsx
110
+ │ │ │ ├── dashboard/
111
+ │ │ │ │ ├── stats-card.tsx
112
+ │ │ │ │ └── recent-activity.tsx
113
+ │ │ │ └── posts/
114
+ │ │ │ ├── post-list.tsx
115
+ │ │ │ ├── post-item.tsx
116
+ │ │ │ └── post-create.tsx
117
+ │ │ │
118
+ │ │ └── common/ # 공통 컴포넌트
119
+ │ │ ├── loading-spinner.tsx
120
+ │ │ ├── error-boundary.tsx
121
+ │ │ ├── confirmation-dialog.tsx
122
+ │ │ └── theme-provider.tsx
123
+ │ │
124
+ │ ├── hooks/ # 커스텀 훅
125
+ │ │ ├── use-auth.ts
126
+ │ │ ├── use-local-storage.ts
127
+ │ │ ├── use-local-db.ts
128
+ │ │ ├── use-debounce.ts
129
+ │ │ └── use-media-query.ts
130
+ │ │
131
+ │ ├── lib/ # 라이브러리 설정 및 유틸리티
132
+ │ │ ├── utils.ts # 공통 유틸 함수 (cn 함수 등)
133
+ │ │ ├── validations.ts # Zod 스키마
134
+ │ │ ├── constants.ts # 앱 전역 상수
135
+ │ │ │
136
+ │ │ ├── auth/ # 인증 관련
137
+ │ │ │ ├── config.ts
138
+ │ │ │ └── providers.ts
139
+ │ │ │
140
+ │ │ └── db/ # 로컬 스토리지 DB 구현
141
+ │ │ ├── storage.ts # 로컬 스토리지 래퍼
142
+ │ │ ├── database-service.ts # DB 서비스 레이어
143
+ │ │ ├── mock-data.ts # 초기 목 데이터
144
+ │ │ ├── repositories/
145
+ │ │ │ ├── base-repository.ts
146
+ │ │ │ ├── user-repository.ts
147
+ │ │ │ ├── post-repository.ts
148
+ │ │ │ ├── comment-repository.ts
149
+ │ │ │ └── tag-repository.ts
150
+ │ │ ├── models/
151
+ │ │ │ ├── user.ts
152
+ │ │ │ ├── post.ts
153
+ │ │ │ ├── comment.ts
154
+ │ │ │ └── tag.ts
155
+ │ │ └── utils/
156
+ │ │ ├── query-builder.ts
157
+ │ │ ├── relationships.ts
158
+ │ │ └── validation.ts
159
+ │ │
160
+ │ ├── states/ # 전역 상태 (Jotai atoms)
161
+ │ │ ├── auth-store.ts
162
+ │ │ ├── ui-store.ts
163
+ │ │ └── user-store.ts
164
+ │ │
165
+ │ ├── styles/ # 스타일 관련
166
+ │ │ ├── globals.css # Tailwind 설정
167
+ │ │ └── components.css # 커스텀 컴포넌트 스타일
168
+ │ │
169
+ │ └── types/ # TypeScript 타입 정의
170
+ │ ├── auth.ts
171
+ │ ├── database.ts
172
+ │ ├── api.ts
173
+ │ └── global.d.ts
174
+
175
+ ├── tests/ # 테스트 파일
176
+ │ ├── __mocks__/
177
+ │ ├── setup.ts
178
+ │ ├── components/
179
+ │ │ └── button.test.tsx
180
+ │ ├── hooks/
181
+ │ │ └── use-auth.test.ts
182
+ │ └── pages/
183
+ │ └── home.test.tsx
184
+
185
+ ├── docs/ # 프로젝트 문서
186
+ │ ├── api.md
187
+ │ ├── deployment.md
188
+ │ └── contributing.md
189
+
190
+ └── scripts/ # 빌드/배포 스크립트
191
+ ├── build.sh
192
+ └── deploy.sh
193
+ ```
194
+
195
+ ## 🛠️ 핵심 설정 파일
196
+
197
+ ### package.json
198
+ ```json
199
+ {
200
+ "name": "my-nextjs-app",
201
+ "version": "0.1.0",
202
+ "private": true,
203
+ "scripts": {
204
+ "dev": "next dev",
205
+ "build": "next build",
206
+ "start": "next start",
207
+ "lint": "next lint",
208
+ "test": "vitest",
209
+ "test:watch": "vitest --watch"
210
+ },
211
+ "dependencies": {
212
+ "next": "^15.0.0",
213
+ "react": "^19.0.0",
214
+ "react-dom": "^19.0.0",
215
+ "@tanstack/react-query": "^5.0.0",
216
+ "jotai": "^2.0.0",
217
+ "lucide-react": "^0.400.0",
218
+ "tailwindcss": "^3.4.0",
219
+ "zod": "^3.22.0"
220
+ },
221
+ "devDependencies": {
222
+ "@types/node": "^20.0.0",
223
+ "@types/react": "^19.0.0",
224
+ "@types/react-dom": "^19.0.0",
225
+ "typescript": "^5.0.0",
226
+ "vitest": "^2.0.0",
227
+ "@vitejs/plugin-react": "^4.0.0",
228
+ "jsdom": "^25.0.0",
229
+ "@testing-library/react": "^16.0.0"
230
+ }
231
+ }
232
+ ```
233
+
234
+ ### vitest.config.mts
235
+ ```typescript
236
+ import { defineConfig } from 'vitest/config'
237
+ import react from '@vitejs/plugin-react'
238
+ import tsconfigPaths from 'vite-tsconfig-paths'
239
+
240
+ export default defineConfig({
241
+ plugins: [tsconfigPaths(), react()],
242
+ test: {
243
+ environment: 'jsdom',
244
+ setupFiles: ['./tests/setup.ts'],
245
+ },
246
+ })
247
+ ```
248
+
249
+ ## 🗄️ 로컬 스토리지 DB 구조 및 구현
250
+
251
+ ### 로컬 스토리지 DB 아키텍처
252
+
253
+ ```typescript
254
+ // src/lib/db/storage.ts - 로컬 스토리지 래퍼
255
+ export class LocalStorage {
256
+ private static instance: LocalStorage
257
+ private prefix = 'myapp_'
258
+
259
+ static getInstance(): LocalStorage {
260
+ if (!LocalStorage.instance) {
261
+ LocalStorage.instance = new LocalStorage()
262
+ }
263
+ return LocalStorage.instance
264
+ }
265
+
266
+ set<T>(key: string, value: T): void {
267
+ try {
268
+ localStorage.setItem(
269
+ `${this.prefix}${key}`,
270
+ JSON.stringify(value)
271
+ )
272
+ } catch (error) {
273
+ console.error('LocalStorage set error:', error)
274
+ }
275
+ }
276
+
277
+ get<T>(key: string): T | null {
278
+ try {
279
+ const item = localStorage.getItem(`${this.prefix}${key}`)
280
+ return item ? JSON.parse(item) : null
281
+ } catch (error) {
282
+ console.error('LocalStorage get error:', error)
283
+ return null
284
+ }
285
+ }
286
+
287
+ remove(key: string): void {
288
+ localStorage.removeItem(`${this.prefix}${key}`)
289
+ }
290
+
291
+ clear(): void {
292
+ Object.keys(localStorage)
293
+ .filter(key => key.startsWith(this.prefix))
294
+ .forEach(key => localStorage.removeItem(key))
295
+ }
296
+ }
297
+ ```
298
+
299
+ ### 베이스 리포지토리 패턴
300
+
301
+ ```typescript
302
+ // src/lib/db/repositories/base-repository.ts
303
+ export abstract class BaseRepository<T extends { id: string }> {
304
+ protected storage = LocalStorage.getInstance()
305
+ protected abstract tableName: string
306
+
307
+ protected generateId(): string {
308
+ return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
309
+ }
310
+
311
+ async findAll(): Promise<T[]> {
312
+ const data = this.storage.get<T[]>(this.tableName)
313
+ return data || []
314
+ }
315
+
316
+ async findById(id: string): Promise<T | null> {
317
+ const items = await this.findAll()
318
+ return items.find(item => item.id === id) || null
319
+ }
320
+
321
+ async create(data: Omit<T, 'id'>): Promise<T> {
322
+ const items = await this.findAll()
323
+ const newItem = { ...data, id: this.generateId() } as T
324
+ items.push(newItem)
325
+ this.storage.set(this.tableName, items)
326
+ return newItem
327
+ }
328
+
329
+ async update(id: string, data: Partial<T>): Promise<T | null> {
330
+ const items = await this.findAll()
331
+ const index = items.findIndex(item => item.id === id)
332
+
333
+ if (index === -1) return null
334
+
335
+ items[index] = { ...items[index], ...data }
336
+ this.storage.set(this.tableName, items)
337
+ return items[index]
338
+ }
339
+
340
+ async delete(id: string): Promise<boolean> {
341
+ const items = await this.findAll()
342
+ const filteredItems = items.filter(item => item.id !== id)
343
+
344
+ if (filteredItems.length === items.length) return false
345
+
346
+ this.storage.set(this.tableName, filteredItems)
347
+ return true
348
+ }
349
+
350
+ async deleteAll(): Promise<void> {
351
+ this.storage.remove(this.tableName)
352
+ }
353
+ }
354
+ ```
355
+
356
+ ### 모델 정의
357
+
358
+ ```typescript
359
+ // src/lib/db/models/user.ts
360
+ export interface User {
361
+ id: string
362
+ email: string
363
+ name: string
364
+ avatar?: string
365
+ createdAt: Date
366
+ updatedAt: Date
367
+ }
368
+
369
+ // src/lib/db/models/post.ts
370
+ export interface Post {
371
+ id: string
372
+ title: string
373
+ content: string
374
+ authorId: string
375
+ tags: string[]
376
+ status: 'draft' | 'published' | 'archived'
377
+ createdAt: Date
378
+ updatedAt: Date
379
+ }
380
+
381
+ // src/lib/db/models/comment.ts
382
+ export interface Comment {
383
+ id: string
384
+ content: string
385
+ postId: string
386
+ authorId: string
387
+ parentId?: string // 대댓글용
388
+ createdAt: Date
389
+ updatedAt: Date
390
+ }
391
+ ```
392
+
393
+ ### 구체적인 리포지토리 구현
394
+
395
+ ```typescript
396
+ // src/lib/db/repositories/user-repository.ts
397
+ import { BaseRepository } from './base-repository'
398
+ import { User } from '../models/user'
399
+
400
+ export class UserRepository extends BaseRepository<User> {
401
+ protected tableName = 'users'
402
+
403
+ async findByEmail(email: string): Promise<User | null> {
404
+ const users = await this.findAll()
405
+ return users.find(user => user.email === email) || null
406
+ }
407
+
408
+ async findByName(name: string): Promise<User[]> {
409
+ const users = await this.findAll()
410
+ return users.filter(user =>
411
+ user.name.toLowerCase().includes(name.toLowerCase())
412
+ )
413
+ }
414
+ }
415
+
416
+ // src/lib/db/repositories/post-repository.ts
417
+ import { BaseRepository } from './base-repository'
418
+ import { Post } from '../models/post'
419
+
420
+ export class PostRepository extends BaseRepository<Post> {
421
+ protected tableName = 'posts'
422
+
423
+ async findByAuthor(authorId: string): Promise<Post[]> {
424
+ const posts = await this.findAll()
425
+ return posts.filter(post => post.authorId === authorId)
426
+ }
427
+
428
+ async findByStatus(status: Post['status']): Promise<Post[]> {
429
+ const posts = await this.findAll()
430
+ return posts.filter(post => post.status === status)
431
+ }
432
+
433
+ async findByTag(tag: string): Promise<Post[]> {
434
+ const posts = await this.findAll()
435
+ return posts.filter(post => post.tags.includes(tag))
436
+ }
437
+
438
+ async search(query: string): Promise<Post[]> {
439
+ const posts = await this.findAll()
440
+ return posts.filter(post =>
441
+ post.title.toLowerCase().includes(query.toLowerCase()) ||
442
+ post.content.toLowerCase().includes(query.toLowerCase())
443
+ )
444
+ }
445
+ }
446
+ ```
447
+
448
+ ### 초기 목 데이터
449
+
450
+ ```typescript
451
+ // src/lib/db/mock-data.ts
452
+ import { User } from './models/user'
453
+ import { Post } from './models/post'
454
+ import { Comment } from './models/comment'
455
+
456
+ export const mockUsers: User[] = [
457
+ {
458
+ id: '1',
459
+ email: 'john@example.com',
460
+ name: 'John Doe',
461
+ avatar: 'https://avatar.vercel.sh/john',
462
+ createdAt: new Date('2024-01-01'),
463
+ updatedAt: new Date('2024-01-01')
464
+ },
465
+ {
466
+ id: '2',
467
+ email: 'jane@example.com',
468
+ name: 'Jane Smith',
469
+ avatar: 'https://avatar.vercel.sh/jane',
470
+ createdAt: new Date('2024-01-02'),
471
+ updatedAt: new Date('2024-01-02')
472
+ }
473
+ ]
474
+
475
+ export const mockPosts: Post[] = [
476
+ {
477
+ id: '1',
478
+ title: 'Getting Started with Next.js',
479
+ content: 'Next.js is a powerful React framework...',
480
+ authorId: '1',
481
+ tags: ['nextjs', 'react', 'tutorial'],
482
+ status: 'published',
483
+ createdAt: new Date('2024-01-10'),
484
+ updatedAt: new Date('2024-01-10')
485
+ },
486
+ {
487
+ id: '2',
488
+ title: 'Advanced TypeScript Tips',
489
+ content: 'Here are some advanced TypeScript techniques...',
490
+ authorId: '2',
491
+ tags: ['typescript', 'javascript', 'tips'],
492
+ status: 'published',
493
+ createdAt: new Date('2024-01-15'),
494
+ updatedAt: new Date('2024-01-15')
495
+ }
496
+ ]
497
+
498
+ export const mockComments: Comment[] = [
499
+ {
500
+ id: '1',
501
+ content: 'Great article! Very helpful.',
502
+ postId: '1',
503
+ authorId: '2',
504
+ createdAt: new Date('2024-01-11'),
505
+ updatedAt: new Date('2024-01-11')
506
+ }
507
+ ]
508
+
509
+ // 초기 데이터 로드 함수
510
+ export function initializeMockData(): void {
511
+ const storage = LocalStorage.getInstance()
512
+
513
+ // 이미 데이터가 있는지 확인
514
+ const existingUsers = storage.get<User[]>('users')
515
+ if (!existingUsers || existingUsers.length === 0) {
516
+ storage.set('users', mockUsers)
517
+ storage.set('posts', mockPosts)
518
+ storage.set('comments', mockComments)
519
+ console.log('Mock data initialized')
520
+ }
521
+ }
522
+ ```
523
+
524
+ ### 데이터베이스 서비스 레이어
525
+
526
+ ```typescript
527
+ // src/lib/db/database-service.ts
528
+ import { UserRepository } from './repositories/user-repository'
529
+ import { PostRepository } from './repositories/post-repository'
530
+ import { CommentRepository } from './repositories/comment-repository'
531
+ import { initializeMockData } from './mock-data'
532
+
533
+ export class DatabaseService {
534
+ private static instance: DatabaseService
535
+
536
+ public users: UserRepository
537
+ public posts: PostRepository
538
+ public comments: CommentRepository
539
+
540
+ private constructor() {
541
+ this.users = new UserRepository()
542
+ this.posts = new PostRepository()
543
+ this.comments = new CommentRepository()
544
+
545
+ // 초기 데이터 로드
546
+ initializeMockData()
547
+ }
548
+
549
+ static getInstance(): DatabaseService {
550
+ if (!DatabaseService.instance) {
551
+ DatabaseService.instance = new DatabaseService()
552
+ }
553
+ return DatabaseService.instance
554
+ }
555
+
556
+ // 모든 테이블 초기화
557
+ async clearAllData(): Promise<void> {
558
+ await this.users.deleteAll()
559
+ await this.posts.deleteAll()
560
+ await this.comments.deleteAll()
561
+ }
562
+
563
+ // 백업 생성
564
+ async backup(): Promise<string> {
565
+ const data = {
566
+ users: await this.users.findAll(),
567
+ posts: await this.posts.findAll(),
568
+ comments: await this.comments.findAll(),
569
+ timestamp: new Date().toISOString()
570
+ }
571
+ return JSON.stringify(data, null, 2)
572
+ }
573
+
574
+ // 백업 복원
575
+ async restore(backupData: string): Promise<void> {
576
+ try {
577
+ const data = JSON.parse(backupData)
578
+
579
+ await this.clearAllData()
580
+
581
+ // 데이터 복원
582
+ const storage = LocalStorage.getInstance()
583
+ storage.set('users', data.users)
584
+ storage.set('posts', data.posts)
585
+ storage.set('comments', data.comments)
586
+
587
+ console.log('Data restored successfully')
588
+ } catch (error) {
589
+ console.error('Failed to restore data:', error)
590
+ throw error
591
+ }
592
+ }
593
+ }
594
+
595
+ // 편의를 위한 싱글톤 인스턴스 export
596
+ export const db = DatabaseService.getInstance()
597
+ ```
598
+
599
+ ### 사용 예시
600
+
601
+ ```typescript
602
+ // src/hooks/use-local-db.ts
603
+ import { useState, useEffect } from 'react'
604
+ import { db } from '@/lib/db/database-service'
605
+ import { User, Post } from '@/lib/db/models'
606
+
607
+ export function useUsers() {
608
+ const [users, setUsers] = useState<User[]>([])
609
+ const [loading, setLoading] = useState(true)
610
+
611
+ useEffect(() => {
612
+ const loadUsers = async () => {
613
+ try {
614
+ const data = await db.users.findAll()
615
+ setUsers(data)
616
+ } catch (error) {
617
+ console.error('Failed to load users:', error)
618
+ } finally {
619
+ setLoading(false)
620
+ }
621
+ }
622
+
623
+ loadUsers()
624
+ }, [])
625
+
626
+ const createUser = async (userData: Omit<User, 'id'>) => {
627
+ try {
628
+ const newUser = await db.users.create(userData)
629
+ setUsers(prev => [...prev, newUser])
630
+ return newUser
631
+ } catch (error) {
632
+ console.error('Failed to create user:', error)
633
+ throw error
634
+ }
635
+ }
636
+
637
+ return { users, loading, createUser }
638
+ }
639
+
640
+ // 컴포넌트에서 사용
641
+ function UserList() {
642
+ const { users, loading, createUser } = useUsers()
643
+
644
+ if (loading) return <div>Loading...</div>
645
+
646
+ return (
647
+ <div>
648
+ {users.map(user => (
649
+ <div key={user.id}>{user.name}</div>
650
+ ))}
651
+ </div>
652
+ )
653
+ }
654
+ ```
655
+
656
+ ## 🎯 개발 워크플로우
657
+
658
+ ### 1. 기능 구현 프로세스
659
+ 1. **요구사항 분석** → 구체적인 구현 계획 수립
660
+ 2. **계획 검토** → 사용자 승인 후 진행
661
+ 3. **단계적 구현** → 작은 단위로 세분화하여 진행
662
+ 4. **로깅 및 디버깅** → 각 단계마다 충분한 로그 추가
663
+ 5. **테스트 및 검증** → 각 단계별 동작 확인
664
+
665
+ ### 2. 명명 규칙
666
+ - **파일명**: `kebab-case` (예: `user-profile.tsx`)
667
+ - **컴포넌트**: `PascalCase` (예: `UserProfile`)
668
+ - **함수/변수**: `camelCase` (예: `getUserData`)
669
+ - **상수**: `UPPER_SNAKE_CASE` (예: `API_BASE_URL`)
670
+
671
+ ### 3. 컴포넌트 구조 패턴
672
+ ```typescript
673
+ // 1. Imports (외부 라이브러리 → 내부 모듈)
674
+ import { useState } from 'react'
675
+ import { Button } from '@/components/ui/button'
676
+
677
+ // 2. Types/Interfaces
678
+ interface UserProfileProps {
679
+ userId: string
680
+ }
681
+
682
+ // 3. Main Component
683
+ export function UserProfile({ userId }: UserProfileProps) {
684
+ // 4. Hooks and State
685
+ const [loading, setLoading] = useState(false)
686
+
687
+ // 5. Event Handlers
688
+ const handleSave = () => {
689
+ // 구현
690
+ }
691
+
692
+ // 6. Early Returns
693
+ if (loading) return <div>Loading...</div>
694
+
695
+ // 7. Main Render
696
+ return (
697
+ <div>
698
+ {/* 컴포넌트 내용 */}
699
+ </div>
700
+ )
701
+ }
702
+
703
+ // 8. Sub-components (필요한 경우)
704
+ function ProfileHeader() {
705
+ return <div>Header</div>
706
+ }
707
+ ```
708
+
709
+ ## 🚀 React 19 & Next.js 15 최적화
710
+
711
+ ### Server Components 우선 사용
712
+ - **'use client' 최소화** - 클라이언트 컴포넌트 사용을 필요한 경우에만 제한
713
+ - **Server Actions 활용** - API 라우트 대신 Server Actions 우선 사용
714
+
715
+ ### Async Runtime APIs 사용
716
+ ```typescript
717
+ // 올바른 사용법
718
+ const cookieStore = await cookies()
719
+ const headersList = await headers()
720
+ const params = await props.params
721
+ ```
722
+
723
+ ### 성능 최적화
724
+ - **Code Splitting** - 동적 import 활용
725
+ - **Image Optimization** - Next.js Image 컴포넌트 사용
726
+ - **Bundle Analysis** - `@next/bundle-analyzer` 활용
727
+
728
+ ### 보안
729
+ - **환경 변수 관리** - `.env.local` 사용
730
+ - **CSRF 보호** - Server Actions 활용
731
+ - **타입 안전성** - Zod를 통한 런타임 검증