refactor: .claude 규칙 체계를 skills·rules 구조로 재편
- 슬래시 커맨드(nestjs/vue3/flutter)를 정식 스킬(.claude/skills)로 전환하고 docker 스킬을 신설(루트 command.md 이관) - 공통 규칙을 .claude/rules 로 모듈화(coding-common·response-format·error-codes· git-convention·security·env-structure·testing·checklist)하고 CLAUDE.md 에서 @import - 파일·식별자 네이밍 규약, 테스트 규칙 추가 - .claude/settings.json(권한·환경) 추가 - CLAUDE.md·README.md 참조 갱신, 단독 실행(non-Docker) 안내 정리 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
---
|
||||
name: vue3
|
||||
description: Vue 3 프론트엔드(frontend/) 개발 규칙 — Composition API + <script setup>, Props/Emits 타입 정의, Pinia 스토어, Composables+Axios API 호출, Vue Router 코드 스플리팅·가드. frontend/ 에서 작업하거나 컴포넌트·스토어·컴포저블·라우터를 만들 때 사용한다.
|
||||
---
|
||||
|
||||
> 이 스킬은 `.claude/skills/vue3/SKILL.md`에 위치한다.
|
||||
> 사용법: `/vue3 [요청 내용]` 또는 frontend/ 작업 시 자동 적용.
|
||||
> 공통 규칙: [[response-format]] · [[error-codes]] · [[security]] · [[coding-common]]
|
||||
|
||||
---
|
||||
|
||||
## 템플릿 기본 제공 골격
|
||||
|
||||
> 이 템플릿은 아래를 **기본 제공**한다. 새 코드는 이 파일들의 패턴을 복제한다(같은 역할을 새로 만들지 말 것).
|
||||
|
||||
| 제공 항목 | 위치 | 비고 |
|
||||
| --- | --- | --- |
|
||||
| 중앙 Axios 인스턴스 | `src/composables/useApi.ts` | baseURL=`VITE_API_BASE_URL`, `withCredentials` |
|
||||
| 응답 언래핑 인터셉터 | `useApi.ts` | `{success,data}→data`, 에러 코드 처리 |
|
||||
| ErrorCodeLexicon | `useApi.ts` 내부 | 에러 코드 → 한국어 메시지 ([[error-codes]]) |
|
||||
| 도메인 Composable 예제 | `src/composables/useUser.ts` | 새 도메인 API 패턴 참고 |
|
||||
| 인증 Composable 예제 | `src/composables/useAuth.ts` | 로그인/로그아웃 흐름(쿠키 기반) 시작점 |
|
||||
| Pinia 스토어 예제 | `src/stores/user.store.ts` | 새 스토어 패턴 참고 |
|
||||
| 인증 스토어 | `src/stores/auth.store.ts` | 세션 인증 여부(`isLoggedIn`) 관리 |
|
||||
| 공통 컴포넌트 예제 | `src/components/BaseButton.vue` | 재사용 컴포넌트 기준 패턴(타입 Props/Emits·scoped·rem·a11y) |
|
||||
| 라우터 | `src/router/index.ts` | lazy import + `auth.store` 연동 `requiresAuth` 가드 |
|
||||
| env 타입 선언 | `frontend/env.d.ts` | `VITE_API_BASE_URL` |
|
||||
|
||||
> **확장 시 따르는 규칙:**
|
||||
> - **로그인 연동**: 실제 `/auth/login` 엔드포인트 연결 → 성공 시 `useAuth` 가 `authStore.markAuthenticated()` 호출
|
||||
> - **로그인 페이지**: 추가 시 라우터 가드 리다이렉트를 `{ name: 'login' }` 으로 변경
|
||||
> - **공통 컴포넌트**: `BaseButton` 패턴을 따라 `src/components/` 에 추가
|
||||
|
||||
---
|
||||
|
||||
## 컴포넌트 작성 원칙
|
||||
|
||||
### 필수 구조
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
// 1. import
|
||||
// 2. Props / Emits 정의
|
||||
// 3. Store / Composable 호출
|
||||
// 4. 반응형 상태 (ref, reactive, computed)
|
||||
// 5. 함수 정의
|
||||
// 6. Lifecycle Hooks
|
||||
// 7. watch
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 시맨틱 HTML 태그 사용 -->
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* rem 단위 사용, 인라인 스타일 금지 */
|
||||
</style>
|
||||
```
|
||||
|
||||
- `Options API` 사용 **금지** → `Composition API` + `<script setup>` 필수
|
||||
- 인라인 스타일(`style=""`) 사용 **금지** → `<style scoped>` 사용
|
||||
- 모든 px 값은 **rem으로 변환** (기준: 16px = 1rem)
|
||||
|
||||
---
|
||||
|
||||
## Props / Emits 타입 정의
|
||||
|
||||
```typescript
|
||||
// Props
|
||||
interface Props {
|
||||
title: string
|
||||
count?: number
|
||||
items: string[]
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
count: 0,
|
||||
})
|
||||
|
||||
// Emits
|
||||
interface Emits {
|
||||
(e: 'update', value: string): void
|
||||
(e: 'close'): void
|
||||
}
|
||||
const emit = defineEmits<Emits>()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 상태 관리 (Pinia)
|
||||
|
||||
```typescript
|
||||
// stores/user.store.ts
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
// 상태
|
||||
const user = ref<User | null>(null)
|
||||
const isLoggedIn = computed(() => !!user.value)
|
||||
|
||||
// 액션
|
||||
async function fetchUser(id: number) {
|
||||
// API 호출
|
||||
}
|
||||
|
||||
return { user, isLoggedIn, fetchUser }
|
||||
})
|
||||
```
|
||||
|
||||
- 도메인별 Store 파일 분리 (`user.store.ts`, `auth.store.ts` 등)
|
||||
- Store 내 API 호출은 허용, 단 에러 처리는 인터셉터에 위임
|
||||
|
||||
---
|
||||
|
||||
## API 호출 (Composables + Axios)
|
||||
|
||||
```typescript
|
||||
// composables/useApi.ts — 중앙 Axios 인스턴스
|
||||
const api = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // env.d.ts 에 타입 선언
|
||||
withCredentials: true, // HttpOnly 쿠키 전송
|
||||
})
|
||||
|
||||
// 응답 인터셉터 — 표준 응답 언래핑 + 에러 코드 전역 처리
|
||||
api.interceptors.response.use(
|
||||
(response) => response.data.data, // { success, data } → data 언래핑
|
||||
(error) => {
|
||||
const code = error.response?.data?.error?.code
|
||||
// 에러 코드별 Toast 알림 처리 ([[error-codes]] 참조)
|
||||
return Promise.reject(error)
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
```typescript
|
||||
// composables/useUser.ts — 도메인별 API Composable
|
||||
export function useUser() {
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
async function getUser(id: number) {
|
||||
loading.value = true
|
||||
try {
|
||||
return await api.get(`/users/${id}`)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return { loading, error, getUser }
|
||||
}
|
||||
```
|
||||
|
||||
- API 호출 로직은 반드시 `composables/` 폴더에 분리
|
||||
- 컴포넌트 내 `axios.get()` 직접 호출 **금지**
|
||||
- `ErrorCodeLexicon` 은 세 플랫폼 공유 에러 맵의 일부 — [[error-codes]] 참조.
|
||||
|
||||
---
|
||||
|
||||
## 라우터 (Vue Router)
|
||||
|
||||
```typescript
|
||||
// 코드 스플리팅 — 모든 페이지 컴포넌트는 lazy import
|
||||
const routes = [
|
||||
{
|
||||
path: '/dashboard',
|
||||
component: () => import('@/pages/DashboardPage.vue'),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
]
|
||||
|
||||
// 네비게이션 가드 — 인증 처리
|
||||
router.beforeEach((to) => {
|
||||
const authStore = useAuthStore()
|
||||
if (to.meta.requiresAuth && !authStore.isLoggedIn) {
|
||||
return '/login'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 성능 & 최신 트렌드 추가 규칙
|
||||
|
||||
- **컴포넌트 분리**: 200줄 이상 컴포넌트는 분리 고려
|
||||
- **`defineAsyncComponent`**: 무거운 컴포넌트는 비동기 로딩 적용
|
||||
- **`v-memo`**: 반복 렌더링 최적화 필요 시 활용
|
||||
- **`<Suspense>`**: 비동기 컴포넌트 로딩 상태 처리에 활용
|
||||
- **환경변수**: 모든 설정값은 `import.meta.env.VITE_*` 형태로 관리 ([[env-structure]] 참조)
|
||||
- **절대경로**: `@/` 경로 alias 사용 (상대경로 `../../` 지양)
|
||||
- **접근성(a11y)**: `aria-label`, `role`, `tabindex` 등 기본 접근성 속성 포함
|
||||
|
||||
---
|
||||
|
||||
## 작업 점검 체크리스트
|
||||
|
||||
(전체 확장 지점·점검 목록은 [[checklist]])
|
||||
|
||||
- [ ] **API 호출**: 컴포넌트에서 `axios` 직접 호출 금지 → `composables/` 경유.
|
||||
- [ ] **에러 처리**: 개별 try/catch 로 메시지를 만들지 말고 `useApi` 인터셉터 + [[error-codes]] 에 위임.
|
||||
- [ ] **컴포넌트**: `<script setup lang="ts">` + `<style scoped>` + rem 단위. 인라인 스타일·Options API 금지.
|
||||
- [ ] **타입**: Props/Emits 는 인터페이스로 정의.
|
||||
- [ ] **라우트**: 페이지는 lazy import, 보호 라우트는 `meta.requiresAuth`.
|
||||
- [ ] **auth 가드(인증 도입 시)**: `router/index.ts` 의 `TODO` — `auth.store.ts` 작성 후 `isLoggedIn` 검사 연결.
|
||||
- [ ] **lint**: 커밋 전 `npm run lint` + `npm run type-check` 통과.
|
||||
- [ ] **env**: `VITE_API_BASE_URL` 은 빌드 시 루트 `BACKEND_API_URL` 에서 주입된다 — `frontend/.env` 에 별도 작성 불필요.
|
||||
Reference in New Issue
Block a user