Files
claude-instructions/rules/frontend/code-style.md

116 lines
3.0 KiB
Markdown

# 코드 스타일 & 네이밍 컨벤션
> Nuxt 4 + TypeScript strict 환경 기준
> 최종 업데이트: 2026-04-07
---
## 디렉토리 구조
```
my-nuxt-app/
├─ app/
│ ├─ assets/
│ ├─ components/ # PascalCase.vue
│ │ └─ ui/ # shadcn-vue 컴포넌트
│ ├─ composables/ # useXxx.ts
│ ├─ layouts/ # kebab-case.vue
│ ├─ middleware/ # kebab-case.ts
│ ├─ pages/ # kebab-case.vue
│ ├─ plugins/ # kebab-case.ts
│ ├─ stores/ # useXxxStore.ts
│ ├─ utils/ # camelCase.ts
│ ├─ app.vue
│ ├─ app.config.ts # 테마/UI 등 공개 설정값
│ └─ error.vue
├─ server/
│ ├─ api/ # kebab-case.ts
│ ├─ middleware/
│ └─ utils/
├─ shared/ # 클라이언트/서버 공유 타입 및 유틸
├─ public/
├─ nuxt.config.ts
├─ tailwind.config.ts
└─ tsconfig.json
```
---
## 네이밍 컨벤션
### 파일명
| 파일 종류 | 규칙 | 예시 |
|-----------|------|------|
| 컴포넌트 | `PascalCase.vue` | `UserProfile.vue` |
| 페이지 / 레이아웃 / 미들웨어 / 플러그인 | `kebab-case` | `user-profile.vue` |
| 컴포저블 | `useXxx.ts` | `useUserProfile.ts` |
| 유틸 함수 | `camelCase.ts` | `formatDate.ts` |
| Pinia store | `useXxxStore.ts` | `useAuthStore.ts` |
| API 라우트 | `kebab-case.ts` | `user-profile.ts` |
| 테스트 파일 | `*.spec.ts` / `*.test.ts` | `UserProfile.spec.ts` |
### 코드 내
| 대상 | 규칙 | 예시 |
|------|------|------|
| 변수 / 함수 | `camelCase` | `const userName`, `function fetchUser()` |
| 컴포넌트명 | `PascalCase` | `UserProfile`, `AppHeader` |
| **상수** | **`UPPER_SNAKE_CASE`** | `const MAX_RETRY_COUNT = 3` |
| 타입 / 인터페이스 | `PascalCase` | `interface UserProfile` |
```typescript
// DO
const MAX_PAGE_SIZE = 100
const DEFAULT_LOCALE = 'ko'
// DON'T
const maxPageSize = 100 // 금지: 상수에 camelCase
const defaultLocale = 'ko' // 금지
```
---
## TypeScript 엄격 모드
### any 타입 사용 금지
`any`는 타입 안전성을 무력화한다. `unknown` 또는 명시적 타입을 사용한다.
```typescript
// DON'T
async function fetchUser(id: any): Promise<any> {
return await $fetch(`/api/users/${id}`)
}
// DO
async function fetchUser(id: string): Promise<User> {
return await $fetch<User>(`/api/users/${id}`)
}
// unknown 사용 시 타입 좁히기
function parseResponse(raw: unknown): User {
if (!isUser(raw)) throw new Error('유효하지 않은 응답입니다.')
return raw
}
```
### Props 타입 명시
```vue
<script setup lang="ts">
// DO: interface로 분리 선언
interface Props {
title: string
count: number
variant?: 'primary' | 'secondary'
}
const props = withDefaults(defineProps<Props>(), {
variant: 'primary',
})
// DON'T: 타입 없는 defineProps
// const props = defineProps(['title', 'count'])
</script>
```