@choblue/claude-code-toolkit 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.
@@ -0,0 +1,424 @@
1
+ # Next.js Skill - App Router 규칙
2
+
3
+ Next.js App Router 기반 프로젝트에 적용되는 규칙이다.
4
+ React 핵심 규칙은 `../React/SKILL.md`, 공통 원칙은 `../Coding/SKILL.md`를 함께 참고한다.
5
+
6
+ ---
7
+
8
+ ## 1. App Router 기본 구조
9
+
10
+ ### 예약 파일명
11
+ App Router는 다음 예약 파일명을 사용한다.
12
+
13
+ | 파일명 | 역할 | 설명 |
14
+ |--------|------|------|
15
+ | `page.tsx` | 페이지 | 해당 라우트의 UI |
16
+ | `layout.tsx` | 레이아웃 | 하위 라우트 공유 레이아웃 |
17
+ | `loading.tsx` | 로딩 UI | Suspense 기반 로딩 상태 |
18
+ | `error.tsx` | 에러 UI | Error Boundary 기반 에러 처리 |
19
+ | `not-found.tsx` | 404 UI | 리소스를 찾을 수 없을 때 |
20
+ | `template.tsx` | 템플릿 | 네비게이션마다 새 인스턴스 생성 |
21
+ | `default.tsx` | 기본 UI | Parallel Routes 기본 폴백 |
22
+
23
+ ### 기본 구조 예시
24
+ ```
25
+ app/
26
+ layout.tsx # 루트 레이아웃 (필수)
27
+ page.tsx # 홈 페이지
28
+ loading.tsx # 글로벌 로딩
29
+ error.tsx # 글로벌 에러
30
+ not-found.tsx # 404 페이지
31
+ users/
32
+ page.tsx # /users
33
+ [id]/
34
+ page.tsx # /users/:id
35
+ loading.tsx
36
+ error.tsx
37
+ api/
38
+ users/
39
+ route.ts # API: /api/users
40
+ ```
41
+
42
+ ---
43
+
44
+ ## 2. Server Component vs Client Component
45
+
46
+ ### 기본 원칙
47
+ - **모든 컴포넌트는 기본적으로 Server Component이다**
48
+ - `'use client'`는 실제로 클라이언트 기능이 필요할 때만 선언한다
49
+ - 서버에서 할 수 있는 일은 서버에서 처리한다
50
+
51
+ ### Client Component가 필요한 경우
52
+ - `useState`, `useEffect` 등 React 훅 사용 시
53
+ - 브라우저 API 접근 시 (`window`, `document`, `localStorage`)
54
+ - 이벤트 핸들러 사용 시 (`onClick`, `onChange`)
55
+ - 클라이언트 전용 라이브러리 사용 시
56
+
57
+ ### 분리 전략
58
+ - `'use client'` 경계를 최대한 하위로 밀어내린다
59
+ - 페이지 전체를 Client Component로 만들지 않는다
60
+
61
+ ```typescript
62
+ // Bad - 페이지 전체를 Client Component로 선언
63
+ 'use client';
64
+
65
+ export default function UserPage() {
66
+ const [tab, setTab] = useState('profile');
67
+ const users = await fetchUsers(); // Server에서 가능한 작업인데 Client로 선언
68
+
69
+ return <div>...</div>;
70
+ }
71
+
72
+ // Good - 클라이언트 기능만 분리
73
+ // app/users/page.tsx (Server Component)
74
+ export default async function UserPage() {
75
+ const users = await fetchUsers();
76
+
77
+ return (
78
+ <div>
79
+ <UserList users={users} />
80
+ <UserTabs /> {/* 이것만 Client Component */}
81
+ </div>
82
+ );
83
+ }
84
+
85
+ // components/UserTabs.tsx (Client Component)
86
+ 'use client';
87
+
88
+ export function UserTabs() {
89
+ const [tab, setTab] = useState('profile');
90
+ return <Tabs value={tab} onChange={setTab} />;
91
+ }
92
+ ```
93
+
94
+ ---
95
+
96
+ ## 3. 데이터 페칭
97
+
98
+ ### Server Component에서 직접 fetch
99
+ - Server Component에서 `async/await`로 직접 데이터를 가져온다
100
+ - `useEffect`로 데이터를 가져오지 않는다
101
+
102
+ ```typescript
103
+ // Good - Server Component에서 직접 페칭
104
+ export default async function UserPage({ params }: { params: { id: string } }) {
105
+ const user = await getUser(params.id);
106
+ return <UserProfile user={user} />;
107
+ }
108
+ ```
109
+
110
+ ### Revalidation 전략
111
+ - **Time-based**: `next: { revalidate: 60 }` - 일정 시간마다 갱신
112
+ - **On-demand**: `revalidateTag()`, `revalidatePath()` - 특정 이벤트 시 갱신
113
+
114
+ ```typescript
115
+ // Time-based revalidation
116
+ const data = await fetch('https://api.example.com/users', {
117
+ next: { revalidate: 3600 }, // 1시간마다 갱신
118
+ });
119
+
120
+ // Tag-based revalidation
121
+ const data = await fetch('https://api.example.com/users', {
122
+ next: { tags: ['users'] },
123
+ });
124
+
125
+ // Server Action에서 revalidation 트리거
126
+ 'use server';
127
+ import { revalidateTag } from 'next/cache';
128
+
129
+ export async function createUser(formData: FormData) {
130
+ await saveUser(formData);
131
+ revalidateTag('users');
132
+ }
133
+ ```
134
+
135
+ ---
136
+
137
+ ## 4. Route Handlers
138
+
139
+ ### 위치 및 구조
140
+ - `app/api/` 디렉토리 하위에 `route.ts` 파일로 정의한다
141
+ - HTTP 메서드를 named export로 정의한다
142
+
143
+ ```typescript
144
+ // app/api/users/route.ts
145
+ import { NextRequest, NextResponse } from 'next/server';
146
+
147
+ export async function GET(request: NextRequest) {
148
+ const users = await getUsers();
149
+ return NextResponse.json(users);
150
+ }
151
+
152
+ export async function POST(request: NextRequest) {
153
+ const body = await request.json();
154
+ const user = await createUser(body);
155
+ return NextResponse.json(user, { status: 201 });
156
+ }
157
+ ```
158
+
159
+ ### 동적 라우트
160
+ ```typescript
161
+ // app/api/users/[id]/route.ts
162
+ export async function GET(
163
+ request: NextRequest,
164
+ { params }: { params: { id: string } },
165
+ ) {
166
+ const user = await getUser(params.id);
167
+ if (!user) {
168
+ return NextResponse.json({ error: 'Not Found' }, { status: 404 });
169
+ }
170
+ return NextResponse.json(user);
171
+ }
172
+
173
+ export async function PUT(
174
+ request: NextRequest,
175
+ { params }: { params: { id: string } },
176
+ ) {
177
+ const body = await request.json();
178
+ const user = await updateUser(params.id, body);
179
+ return NextResponse.json(user);
180
+ }
181
+
182
+ export async function DELETE(
183
+ request: NextRequest,
184
+ { params }: { params: { id: string } },
185
+ ) {
186
+ await deleteUser(params.id);
187
+ return new NextResponse(null, { status: 204 });
188
+ }
189
+ ```
190
+
191
+ ---
192
+
193
+ ## 5. Middleware
194
+
195
+ ### 용도
196
+ - 인증/인가 체크
197
+ - 리다이렉트 처리
198
+ - 요청/응답 헤더 수정
199
+ - 국제화(i18n) 라우팅
200
+
201
+ ### 작성 패턴
202
+ ```typescript
203
+ // middleware.ts (프로젝트 루트)
204
+ import { NextResponse } from 'next/server';
205
+ import type { NextRequest } from 'next/server';
206
+
207
+ export function middleware(request: NextRequest) {
208
+ const token = request.cookies.get('token')?.value;
209
+
210
+ if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
211
+ return NextResponse.redirect(new URL('/login', request.url));
212
+ }
213
+
214
+ return NextResponse.next();
215
+ }
216
+
217
+ export const config = {
218
+ matcher: ['/dashboard/:path*', '/admin/:path*'],
219
+ };
220
+ ```
221
+
222
+ ---
223
+
224
+ ## 6. Metadata
225
+
226
+ ### 정적 Metadata
227
+ ```typescript
228
+ // app/about/page.tsx
229
+ import type { Metadata } from 'next';
230
+
231
+ export const metadata: Metadata = {
232
+ title: '소개 - 서비스명',
233
+ description: '서비스 소개 페이지입니다.',
234
+ };
235
+ ```
236
+
237
+ ### 동적 Metadata
238
+ ```typescript
239
+ // app/users/[id]/page.tsx
240
+ import type { Metadata } from 'next';
241
+
242
+ export async function generateMetadata({
243
+ params,
244
+ }: {
245
+ params: { id: string };
246
+ }): Promise<Metadata> {
247
+ const user = await getUser(params.id);
248
+
249
+ return {
250
+ title: `${user.name} - 프로필`,
251
+ description: `${user.name}의 프로필 페이지입니다.`,
252
+ };
253
+ }
254
+ ```
255
+
256
+ ---
257
+
258
+ ## 7. Server Actions
259
+
260
+ ### 정의 및 사용
261
+ - `'use server'` 지시문으로 서버 액션을 정의한다
262
+ - form의 `action` 속성이나 이벤트 핸들러에서 호출한다
263
+
264
+ ```typescript
265
+ // actions/user.ts
266
+ 'use server';
267
+
268
+ import { revalidatePath } from 'next/cache';
269
+
270
+ export async function createUser(formData: FormData) {
271
+ const name = formData.get('name') as string;
272
+ const email = formData.get('email') as string;
273
+
274
+ await db.user.create({ data: { name, email } });
275
+ revalidatePath('/users');
276
+ }
277
+
278
+ export async function deleteUser(userId: string) {
279
+ await db.user.delete({ where: { id: userId } });
280
+ revalidatePath('/users');
281
+ }
282
+ ```
283
+
284
+ ### Form에서 사용
285
+ ```typescript
286
+ // components/CreateUserForm.tsx
287
+ import { createUser } from '@/actions/user';
288
+
289
+ export function CreateUserForm() {
290
+ return (
291
+ <form action={createUser}>
292
+ <input name="name" type="text" required />
293
+ <input name="email" type="email" required />
294
+ <button type="submit">생성</button>
295
+ </form>
296
+ );
297
+ }
298
+ ```
299
+
300
+ ---
301
+
302
+ ## 8. 디렉토리 구조 패턴
303
+
304
+ ### Route Groups - `(group)`
305
+ - URL에 영향을 주지 않고 라우트를 그룹화한다
306
+
307
+ ```
308
+ app/
309
+ (marketing)/
310
+ about/page.tsx # /about
311
+ blog/page.tsx # /blog
312
+ (dashboard)/
313
+ layout.tsx # dashboard 전용 레이아웃
314
+ settings/page.tsx # /settings
315
+ profile/page.tsx # /profile
316
+ ```
317
+
318
+ ### Private Folders - `_folder`
319
+ - 라우팅에서 제외되는 내부 폴더
320
+
321
+ ```
322
+ app/
323
+ _components/ # 라우팅에 포함되지 않음
324
+ Header.tsx
325
+ Footer.tsx
326
+ _lib/ # 내부 유틸리티
327
+ utils.ts
328
+ ```
329
+
330
+ ### Parallel Routes - `@slot`
331
+ - 동일 레이아웃에서 여러 페이지를 동시에 렌더링한다
332
+
333
+ ```
334
+ app/
335
+ layout.tsx # children + @analytics + @team 동시 렌더링
336
+ @analytics/
337
+ page.tsx
338
+ @team/
339
+ page.tsx
340
+ ```
341
+
342
+ ### Intercepting Routes - `(.)`, `(..)`, `(...)`
343
+ - 현재 레이아웃 내에서 다른 라우트를 가로채서 표시한다
344
+
345
+ ```
346
+ app/
347
+ feed/
348
+ page.tsx
349
+ (..)photo/[id]/ # /photo/:id를 모달로 가로챔
350
+ page.tsx
351
+ photo/[id]/
352
+ page.tsx # 직접 접근 시 전체 페이지
353
+ ```
354
+
355
+ ---
356
+
357
+ ## 9. Image/Font 최적화
358
+
359
+ ### next/image
360
+ - 모든 이미지는 `next/image`를 사용한다
361
+ - `width`, `height`를 명시하거나 `fill` 속성을 사용한다
362
+
363
+ ```typescript
364
+ import Image from 'next/image';
365
+
366
+ // 크기 지정
367
+ <Image src="/hero.png" alt="히어로 이미지" width={800} height={400} />
368
+
369
+ // fill 모드 (부모 기준 채움)
370
+ <div className="relative h-64 w-full">
371
+ <Image src="/banner.png" alt="배너" fill className="object-cover" />
372
+ </div>
373
+ ```
374
+
375
+ ### next/font
376
+ - Google Fonts는 `next/font/google`을 사용한다
377
+ - 커스텀 폰트는 `next/font/local`을 사용한다
378
+
379
+ ```typescript
380
+ // app/layout.tsx
381
+ import { Inter } from 'next/font/google';
382
+
383
+ const inter = Inter({ subsets: ['latin'] });
384
+
385
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
386
+ return (
387
+ <html lang="ko" className={inter.className}>
388
+ <body>{children}</body>
389
+ </html>
390
+ );
391
+ }
392
+ ```
393
+
394
+ ---
395
+
396
+ ## 10. 네이밍 컨벤션
397
+
398
+ | 대상 | 규칙 | 예시 |
399
+ |------|------|------|
400
+ | 페이지 파일 | `page.tsx` (예약) | `app/users/page.tsx` |
401
+ | 레이아웃 파일 | `layout.tsx` (예약) | `app/layout.tsx` |
402
+ | 로딩 파일 | `loading.tsx` (예약) | `app/users/loading.tsx` |
403
+ | 에러 파일 | `error.tsx` (예약) | `app/users/error.tsx` |
404
+ | API 라우트 | `route.ts` (예약) | `app/api/users/route.ts` |
405
+ | Server Action 파일 | `camelCase.ts` | `actions/createUser.ts` |
406
+ | Server Action 함수 | `camelCase` | `createUser`, `deletePost` |
407
+ | Route Group | `(groupName)` | `(marketing)`, `(dashboard)` |
408
+ | Private Folder | `_folderName` | `_components`, `_lib` |
409
+ | Dynamic Segment | `[param]` | `[id]`, `[slug]` |
410
+ | Catch-all Segment | `[...param]` | `[...slug]` |
411
+
412
+ ---
413
+
414
+ ## 11. 금지 사항
415
+
416
+ - Client Component에서 무거운 데이터 페칭 로직 작성 금지
417
+ - 불필요한 `'use client'` 선언 금지 - 서버에서 가능하면 서버에서 처리
418
+ - Pages Router 패턴 사용 금지 (`getServerSideProps`, `getStaticProps`, `_app.tsx`, `_document.tsx`)
419
+ - `page.tsx`에 `'use client'` 직접 선언 금지 - 클라이언트 로직은 하위 컴포넌트로 분리
420
+ - Server Component에서 `useState`, `useEffect` 등 클라이언트 훅 사용 금지
421
+ - `<img>` 태그 직접 사용 금지 - `next/image` 사용
422
+ - 외부 폰트를 `<link>` 태그로 로드 금지 - `next/font` 사용
423
+ - `router.push()`를 Server Component에서 사용 금지 - `redirect()` 사용
424
+ - API Route에서 비즈니스 로직 직접 구현 금지 - 별도 서비스 레이어로 분리
@@ -0,0 +1,261 @@
1
+ # React Skill - React 핵심 규칙
2
+
3
+ React 컴포넌트 설계 및 개발에 적용되는 핵심 규칙이다.
4
+ 공통 코딩 원칙은 `../Coding/SKILL.md`를 함께 참고한다.
5
+
6
+ ---
7
+
8
+ ## 1. 컴포넌트 설계 원칙
9
+
10
+ ### 함수형 컴포넌트만 사용한다
11
+ - 클래스 컴포넌트를 사용하지 않는다
12
+ - `React.FC`를 사용하지 않는다 - Props를 직접 타이핑한다
13
+
14
+ ### Props interface 명시
15
+ - 모든 컴포넌트는 Props 타입을 `interface`로 선언한다
16
+ - Props 네이밍은 `컴포넌트명 + Props`로 한다
17
+
18
+ ### SRP (Single Responsibility Principle)
19
+ - 하나의 컴포넌트는 하나의 역할만 수행한다
20
+ - "이 컴포넌트가 하는 일"을 한 문장으로 설명할 수 없으면 분리한다
21
+
22
+ ### 크기 제한
23
+ - 컴포넌트는 최대 200줄을 넘기지 않는다
24
+ - 200줄을 초과하면 하위 컴포넌트로 분리한다
25
+
26
+ ```typescript
27
+ // Bad
28
+ const UserPage = () => {
29
+ // 200줄 이상의 거대한 컴포넌트
30
+ };
31
+
32
+ // Good
33
+ interface UserPageProps {
34
+ userId: string;
35
+ }
36
+
37
+ export function UserPage({ userId }: UserPageProps) {
38
+ const { user, isLoading } = useUser(userId);
39
+
40
+ if (isLoading) return <UserSkeleton />;
41
+ if (!user) return <UserNotFound />;
42
+
43
+ return (
44
+ <div>
45
+ <UserHeader user={user} />
46
+ <UserContent user={user} />
47
+ <UserActions userId={user.id} />
48
+ </div>
49
+ );
50
+ }
51
+ ```
52
+
53
+ ---
54
+
55
+ ## 2. Props 설계
56
+
57
+ ### 최소한의 Props
58
+ - 컴포넌트가 실제로 사용하는 값만 전달한다
59
+ - Props가 5개를 초과하면 설계를 재검토한다
60
+
61
+ ### 콜백 네이밍
62
+ - Props로 전달하는 콜백은 `on` + 동사로 네이밍한다
63
+
64
+ ```typescript
65
+ // Bad
66
+ interface ButtonProps {
67
+ clickHandler: () => void;
68
+ deleteCallback: () => void;
69
+ }
70
+
71
+ // Good
72
+ interface ButtonProps {
73
+ onClick: () => void;
74
+ onDelete: () => void;
75
+ }
76
+ ```
77
+
78
+ ### 객체 통째 전달 지양
79
+ - 필요한 프로퍼티만 개별적으로 전달한다
80
+ - 단, Props가 너무 많아지면 객체 전달을 허용한다
81
+
82
+ ```typescript
83
+ // Bad - 불필요한 데이터까지 전달
84
+ interface UserAvatarProps {
85
+ user: User; // user.name, user.avatar만 사용하는데 전체 객체 전달
86
+ }
87
+
88
+ // Good - 필요한 값만 전달
89
+ interface UserAvatarProps {
90
+ name: string;
91
+ avatarUrl: string;
92
+ }
93
+ ```
94
+
95
+ ---
96
+
97
+ ## 3. 상태 관리 원칙
98
+
99
+ ### 가까운 곳에 배치
100
+ - 상태는 그것을 사용하는 가장 가까운 컴포넌트에 배치한다
101
+ - 상위 컴포넌트로의 lifting은 실제로 필요할 때만 수행한다
102
+
103
+ ### 파생값은 상태가 아니다
104
+ - 기존 상태에서 계산할 수 있는 값은 별도 상태로 만들지 않는다
105
+
106
+ ```typescript
107
+ // Bad - 파생값을 상태로 관리
108
+ const [items, setItems] = useState<Item[]>([]);
109
+ const [itemCount, setItemCount] = useState(0);
110
+ // items가 변경될 때마다 setItemCount를 호출해야 함
111
+
112
+ // Good - 파생값은 계산
113
+ const [items, setItems] = useState<Item[]>([]);
114
+ const itemCount = items.length;
115
+ ```
116
+
117
+ ### 서버 상태와 클라이언트 상태 분리
118
+ - **서버 상태**: API에서 가져온 데이터 -> React Query / SWR 사용
119
+ - **클라이언트 상태**: UI 상태 (모달 열림, 탭 선택 등) -> useState / useReducer 사용
120
+ - 서버 데이터를 `useState`로 복사하지 않는다
121
+
122
+ ---
123
+
124
+ ## 4. 커스텀 훅
125
+
126
+ ### use 접두사
127
+ - 모든 커스텀 훅은 `use`로 시작한다
128
+
129
+ ### 하나의 관심사
130
+ - 하나의 훅은 하나의 관심사만 다룬다
131
+
132
+ ### 데이터 페칭/상태 로직 분리
133
+ - 컴포넌트에서 데이터 페칭과 상태 로직을 커스텀 훅으로 분리한다
134
+
135
+ ```typescript
136
+ // Bad - 컴포넌트에 로직이 섞여 있음
137
+ function UserList() {
138
+ const [users, setUsers] = useState<User[]>([]);
139
+ const [isLoading, setIsLoading] = useState(false);
140
+ const [error, setError] = useState<Error | null>(null);
141
+
142
+ useEffect(() => {
143
+ setIsLoading(true);
144
+ fetchUsers()
145
+ .then(setUsers)
146
+ .catch(setError)
147
+ .finally(() => setIsLoading(false));
148
+ }, []);
149
+
150
+ // ... 렌더링
151
+ }
152
+
153
+ // Good - 커스텀 훅으로 분리
154
+ function useUserList() {
155
+ const { data: users = [], isLoading, error } = useQuery({
156
+ queryKey: ['users'],
157
+ queryFn: fetchUsers,
158
+ });
159
+
160
+ return { users, isLoading, error };
161
+ }
162
+
163
+ function UserList() {
164
+ const { users, isLoading, error } = useUserList();
165
+ // ... 렌더링만 담당
166
+ }
167
+ ```
168
+
169
+ ---
170
+
171
+ ## 5. 렌더링 최적화
172
+
173
+ ### 불필요한 useMemo/useCallback 금지
174
+ - 성능 문제가 실제로 측정된 경우에만 사용한다
175
+ - 참조 동일성이 필요한 경우(의존성 배열, memo된 자식 컴포넌트)에만 사용한다
176
+
177
+ ```typescript
178
+ // Bad - 불필요한 메모이제이션
179
+ const userName = useMemo(() => `${first} ${last}`, [first, last]);
180
+
181
+ // Good - 단순 계산은 그냥 수행
182
+ const userName = `${first} ${last}`;
183
+ ```
184
+
185
+ ### key 올바르게 사용
186
+ - 리스트 렌더링 시 고유한 식별자를 `key`로 사용한다
187
+ - 배열 인덱스를 `key`로 사용하지 않는다 (정적 리스트 제외)
188
+
189
+ ```typescript
190
+ // Bad
191
+ {items.map((item, index) => (
192
+ <Item key={index} data={item} />
193
+ ))}
194
+
195
+ // Good
196
+ {items.map((item) => (
197
+ <Item key={item.id} data={item} />
198
+ ))}
199
+ ```
200
+
201
+ ---
202
+
203
+ ## 6. 조건부 렌더링 패턴
204
+
205
+ ### 단순 조건
206
+ - 2개 이하의 조건은 삼항 연산자 또는 `&&`를 사용한다
207
+
208
+ ```typescript
209
+ // 단순 표시/숨김
210
+ {isVisible && <Modal />}
211
+
212
+ // 이분기
213
+ {isLoading ? <Skeleton /> : <Content />}
214
+ ```
215
+
216
+ ### 복잡한 조건
217
+ - 3개 이상의 분기는 early return 또는 별도 컴포넌트로 분리한다
218
+
219
+ ```typescript
220
+ // Bad - 중첩된 삼항
221
+ {isLoading ? <Skeleton /> : error ? <Error /> : data ? <Content /> : <Empty />}
222
+
223
+ // Good - early return
224
+ function UserContent({ isLoading, error, data }: UserContentProps) {
225
+ if (isLoading) return <Skeleton />;
226
+ if (error) return <ErrorDisplay error={error} />;
227
+ if (!data) return <EmptyState />;
228
+ return <Content data={data} />;
229
+ }
230
+ ```
231
+
232
+ ---
233
+
234
+ ## 7. 네이밍 컨벤션
235
+
236
+ | 대상 | 규칙 | 예시 |
237
+ |------|------|------|
238
+ | 컴포넌트 파일 | `PascalCase.tsx` | `UserCard.tsx` |
239
+ | 훅 파일 | `useCamelCase.ts` | `useAuth.ts` |
240
+ | 유틸 파일 | `camelCase.ts` | `formatDate.ts` |
241
+ | 컴포넌트 | `PascalCase` | `UserCard` |
242
+ | 커스텀 훅 | `useCamelCase` | `useUserList` |
243
+ | 이벤트 핸들러 | `handle` + 대상 + 동작 | `handleUserDelete` |
244
+ | Props 콜백 | `on` + 동작 | `onDelete`, `onChange` |
245
+ | Props 타입 | `컴포넌트명 + Props` | `UserCardProps` |
246
+ | Context | `PascalCase + Context` | `AuthContext` |
247
+ | Provider | `PascalCase + Provider` | `AuthProvider` |
248
+
249
+ ---
250
+
251
+ ## 8. 금지 사항
252
+
253
+ - `any` 타입 사용 금지
254
+ - 인라인 스타일(`style={{}}`) 사용 금지 - 프로젝트 스타일링 방식을 따른다
255
+ - `useEffect` 의존성 배열 누락 금지
256
+ - `index.tsx`에 컴포넌트 로직 직접 작성 금지 (re-export만 허용)
257
+ - 클래스 컴포넌트 사용 금지
258
+ - `React.FC` 사용 금지
259
+ - 배열 인덱스를 `key`로 사용 금지 (정적 리스트 제외)
260
+ - `useEffect` 내에서 상태 동기화 로직 작성 금지 (파생값으로 처리)
261
+ - Props drilling이 3단계 이상일 때 Context 또는 상태 관리 라이브러리 미사용 금지