Compare commits

...

2 Commits

17 changed files with 2831 additions and 1 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
@font-face{font-family:Pretendard;font-weight:900;font-display:swap;src:local('Pretendard Black'),url(woff2/Pretendard-Black.woff2) format('woff2'),url(woff/Pretendard-Black.woff) format('woff')}@font-face{font-family:Pretendard;font-weight:800;font-display:swap;src:local('Pretendard ExtraBold'),url(woff2/Pretendard-ExtraBold.woff2) format('woff2'),url(woff/Pretendard-ExtraBold.woff) format('woff')}@font-face{font-family:Pretendard;font-weight:700;font-display:swap;src:local('Pretendard Bold'),url(woff2/Pretendard-Bold.woff2) format('woff2'),url(woff/Pretendard-Bold.woff) format('woff')}@font-face{font-family:Pretendard;font-weight:600;font-display:swap;src:local('Pretendard SemiBold'),url(woff2/Pretendard-SemiBold.woff2) format('woff2'),url(woff/Pretendard-SemiBold.woff) format('woff')}@font-face{font-family:Pretendard;font-weight:500;font-display:swap;src:local('Pretendard Medium'),url(woff2/Pretendard-Medium.woff2) format('woff2'),url(woff/Pretendard-Medium.woff) format('woff')}@font-face{font-family:Pretendard;font-weight:400;font-display:swap;src:local('Pretendard Regular'),url(woff2/Pretendard-Regular.woff2) format('woff2'),url(woff/Pretendard-Regular.woff) format('woff')}@font-face{font-family:Pretendard;font-weight:300;font-display:swap;src:local('Pretendard Light'),url(woff2/Pretendard-Light.woff2) format('woff2'),url(woff/Pretendard-Light.woff) format('woff')}@font-face{font-family:Pretendard;font-weight:200;font-display:swap;src:local('Pretendard ExtraLight'),url(woff2/Pretendard-ExtraLight.woff2) format('woff2'),url(woff/Pretendard-ExtraLight.woff) format('woff')}@font-face{font-family:Pretendard;font-weight:100;font-display:swap;src:local('Pretendard Thin'),url(woff2/Pretendard-Thin.woff2) format('woff2'),url(woff/Pretendard-Thin.woff) format('woff')}

85
docs/skills-by-phase.md Normal file
View File

@@ -0,0 +1,85 @@
# Phase별 Claude Code Skills 현황
> **기준일:** 2026-04-23 | **로드맵 버전:** v1.2
>
> ✅ 구현 완료 | 🚧 미구현 | ⏸️ 검토 중
---
## Phase 1 — 기획
| Phase | 핵심 활동 | Skill | 상태 |
| --- | --- | --- | --- |
| 1 | 요구사항 정의, 플로우차트 | `requirement-analyzer` | 🚧 |
---
## Phase 2 — 마크업
| Phase | 핵심 활동 | Skill | 상태 |
| --- | --- | --- | --- |
| 2 | HTML 마크업, Tailwind 컨벤션 | `markup` | 🚧 |
| 2-0 | PSD → 피그마 컨버팅 | (외부 도구: Codia AI, PSD Importer) | ⏸️ |
| 2-1 | 피그마 HTML 마크업 | `markup` | 🚧 |
| 2-2 | 프로모션 HTML 마크업 | `markup-promotion` | 🚧 |
| 2-3 | EDM HTML 마크업 | `markup-edm` | ✅ |
---
## Phase 3 — FE개발 (Nuxt 컴포넌트)
| Phase | 핵심 활동 | Skill | 상태 |
| --- | --- | --- | --- |
| 3 | Nuxt 컴포넌트 아키텍처 | `nuxt-component-architect` | 🚧 |
| 3-1 | Nuxt 공식문서 기반 | `nuxt` | 🚧 |
| 3-2 | 페이지·레이아웃·미들웨어 생성 | `nuxt-page-layout` | ✅ |
| 3-3 | Vue 컴포넌트 신규 생성 | `nuxt-component-create` | ✅ |
| 3-4 | Composable·데이터 페칭 | `nuxt-composable` | ✅ |
---
## Phase 4 — FE개발 (API 연동)
| Phase | 핵심 활동 | Skill | 상태 |
| --- | --- | --- | --- |
| 4 | API 연동, 상태관리 | `nuxt-api-state-manager` | 🚧 |
| 4-1 | Pinia 스토어 생성 | `api-pinia-store` | ✅ |
| 4-2 | Nitro 서버 라우트 (BFF) | `api-server-route` | ✅ |
---
## Phase 5 — 검증
| Phase | 핵심 활동 | Skill | 상태 |
| --- | --- | --- | --- |
| 5-1 | 기획 요구사항 검증 | `requirement-optimizer` | 🚧 |
| 5-2 | 성능 최적화 (CWV) | `perf-optimizer` | 🚧 |
---
## Phase 6 — SEO·GEO·AEO
| Phase | 핵심 활동 | Skill | 상태 |
| --- | --- | --- | --- |
| 6 | SEO·GEO·AEO 검증 | `nuxt-seo-geo` | 🚧 |
---
## 공통 Skills (Phase 무관)
| Skill | 용도 | 상태 |
| --- | --- | --- |
| `conventional-commit` | 커밋 메시지 컨벤션 | ✅ |
| `project-init` | 프로젝트 초기 설정 | ✅ |
| `vue-component-review` | 기존 Vue 컴포넌트 리뷰 | ✅ |
| `nuxt-code-review` | 브랜치/PR 단위 근거 기반 코드 리뷰 | ✅ |
---
## 요약
| 구분 | 전체 | 구현 완료 | 미구현 | 검토 중 |
| --- | --- | --- | --- | --- |
| Phase별 Skill | 15 | 6 | 8 | 1 |
| 공통 Skill | 4 | 4 | 0 | 0 |
| **합계** | **19** | **10** | **8** | **1** |

613
docs/standard.md Normal file
View File

@@ -0,0 +1,613 @@
---
title: "Nuxt FE AI 표준화 로드맵 v1.2"
source: "https://sandbox-item.smilegatemegaport.com/Webdev01_UI/fe/fe-ai.html"
author:
published:
created: 2026-04-23
description:
tags:
- "clippings"
---
## Nuxt FE AI Standardization
Claude Code Skills 기반 지능형 업무 매뉴얼 — FE 개발 프로세스 표준화 로드맵
본 문서는 **Nuxt 3 기반 FE 개발 프로세스** 를 Claude Code AI Skills과 통합하여 체계적인 표준화 체계를 구축하기 위한 로드맵입니다. 팀 전체가 동일한 품질과 패턴으로 개발할 수 있도록 각 단계별 **지능형 업무 매뉴얼(Skills)** 을 정의합니다.
### 1.1 프로젝트 목적
- 개발 프로세스 전 단계(기획~검증)에 걸친 AI 어시스턴트 활용 체계 수립
- Claude Code Skills을 활용한 반복 작업 자동화 및 코드 품질 균일화
- 신규 인력의 빠른 온보딩 및 팀 간 일관된 코드 컨벤션 유지
- 개발 생산성 향상과 동시에 코드 리뷰 품질 확보
### 1.2 Claude Code Skills이란?
Claude Code Skills은 특정 작업 도메인에 대해 Claude가 전문적으로 수행할 수 있도록 구조화된 **업무 지시서** 입니다. SKILL.md 파일을 중심으로, 트리거 조건·실행 절차·출력 포맷이 정의되어 있어 팀원 누구나 일관된 결과를 얻을 수 있습니다.
| 구성 요소 | 설명 | 예시 |
| --- | --- | --- |
| **SKILL.md** | 핵심 지시서 (500줄 이내 권장) | 트리거, 절차, 출력 포맷 정의 |
| **scripts/** | 반복 작업 자동화 스크립트 | 컴포넌트 생성기, 린트 검사기 |
| **references/** | 도메인별 참조 문서 | Nuxt 컨벤션, API 패턴 가이드 |
---
FE 개발 전체 라이프사이클의 각 Phase마다 전용 Claude Code Skill을 배치합니다.
🔄
**v1.1 변경사항:** 기존 Phase 5(성능+SEO)를 **Phase 5(성능)****Phase 6(SEO·GEO·AEO)** 로 분리했습니다. SEO Skill에 Generative Engine Optimization(GEO)과 Answer Engine Optimization(AEO)을 추가하여 AI 검색 시대에 대응합니다.
**v1.2 변경사항:** FE개발 단계(Phase 3~4)에 **5개 서브 단계**를 추가했습니다. Phase 3-2(페이지·레이아웃·미들웨어), Phase 3-3(Vue 컴포넌트 생성), Phase 3-4(Composable·데이터 페칭), Phase 4-1(Pinia 스토어), Phase 4-2(Nitro 서버 라우트)로 실제 개발 워크플로우를 세분화합니다.
| 대단계 | Phase | 핵심 활동 | Claude Skill |
| --- | --- | --- | --- |
| 기획 | Phase 1 | 요구사항 정의, 플로우차트 | `requirement-analyzer/SKILL.md requirement-analyzer/references/` |
| 마크업 | Phase 2 | HTML 마크업, Tailwind 컨벤션 | `markup/SKILL.md markup/references/` |
| | Phase 2-0 | PSD -> 피그마 컨버팅 | `SKILL 검토 중 Codia AI 디자인 (PSD to Figma) PSD Importer for Figma` |
| | Phase 2-1 | 피그마 HTML 마크업 | `markup/SKILL.md markup/references/` |
| | Phase 2-2 | 프로모션 HTML 마크업 | `markup-promotion/SKILL.md markup-promotion/references/` |
| | Phase 2-3 | EDM HTML 마크업 | `markup-edm/SKILL.md markup-edm/references/` |
| FE개발 | Phase 3 | Nuxt 컴포넌트 아키텍처 | `component-architect https://github.com/onmax/nuxt-skills/tree/main` |
| | Phase 3-1 | Nuxt 공식문서 기반 | ` nuxt/SKILL.md references/ - server.md - API routes, server middleware - middleware.md - middleware, File-based routing - plugins.md - plugins, app lifecycle - nuxt-composables.md - Nuxt composables - nuxt-components.md - NuxtLink, NuxtImg, NuxtTime - nuxt-config.md - Config, modules, auto-imports, layers ` |
| | Phase 3-2 | 페이지·레이아웃·미들웨어 생성 | `nuxt-page-layout/SKILL.md` |
| | Phase 3-3 | Vue 컴포넌트 신규 생성 | `vue-component-create/SKILL.md` |
| | Phase 3-4 | Composable·데이터 페칭 | `nuxt-composable/SKILL.md` |
| FE개발 | Phase 4 | API 연동 | `api-manager/SKILL.md api-manager/references/` |
| | Phase 4-1 | Pinia 스토어 생성 | `pinia-store/SKILL.md` |
| | Phase 4-2 | Nitro 서버 라우트(BFF) | `nuxt-api-route/SKILL.md` |
| 검증 | Phase 5 | 검증 | |
| | Phase 5-1 | Phase 1 기획 요구사항 검증 | `requirement-optimizer/SKILL.md requirement-optimizer/references/` |
| | Phase 5-2 | 성능 최적화 (CWV) | `perf-optimizer/SKILL.md perf-optimizer/references/` |
| 검증 | Phase 6 | SEO · GEO · AEO 검증 | `seo-geo/SKILL.md seo-geo/references/` |
---
**트리거:** 요구사항, 기획서, 플로우차트, 화면정의서, 유저스토리
기획 문서를 분석하여 Nuxt 프로젝트에 적합한 구조화된 요구사항 명세(페이지 목록, 컴포넌트 트리, 라우팅 맵, 데이터 플로우)를 자동 생성합니다.
"이 기획서를 분석해서 Nuxt 페이지 구조와 컴포넌트 목록을 뽑아줘"
### Skill 동작 절차
1. **입력 분석** — 기획서/PRD 문서를 파싱하여 기능 요구사항, 화면 목록, 사용자 플로우를 추출
2. **페이지 맵 생성** — Nuxt의 pages/ 디렉토리 구조에 맞는 라우팅 맵을 자동 설계
3. **컴포넌트 트리 도출** — Atomic Design 원칙에 따른 컴포넌트 분해
4. **데이터 플로우 정의** — 각 페이지/컴포넌트별 필요 데이터, API 엔드포인트 매핑
5. **Mermaid 플로우차트 출력** — 화면 전환 흐름, 조건 분기를 시각화
💬 프롬프트 템플릿
```
다음 기획서를 분석해서 Nuxt 3 프로젝트 구조를 설계해줘.
[기획서 첨부 또는 내용 붙여넣기]
출력 형식:
1. pages/ 디렉토리 라우팅 구조
2. components/ Atomic Design 기반 컴포넌트 트리
3. 각 페이지별 필요 API 엔드포인트 목록
4. Mermaid 플로우차트 (화면 전환 흐름)
5. composables/ 에 필요한 공통 로직 목록
```
---
**트리거:** 마크업, HTML 구조, 시멘틱, Tailwind, 클래스 순서, 접근성, ARIA, SFC 구조, 반응형
디자인 시안을 Nuxt 컴포넌트의 시멘틱 HTML + Tailwind CSS로 변환합니다. 4개 참조 문서(시멘틱 HTML, Tailwind 컨벤션, 접근성, Nuxt SFC 패턴)를 도메인별로 선택 로드합니다.
"이 디자인을 시멘틱 마크업으로 변환하고 Tailwind 클래스를 적용해줘"
**트리거:** PSD, 피그마 변환, PSD to Figma, 디자인 컨버팅, PSD 임포트
PSD 디자인 파일을 Figma로 변환하는 프로세스를 지원합니다. **Codia AI 디자인(PSD to Figma)****PSD Importer for Figma** 도구를 활용하여 레이어 구조를 유지하며 Figma 포맷으로 컨버팅합니다.
⚠️ SKILL 검토 중 — 도구 안정성 및 레이어 정합성 평가 진행 중
"이 PSD 파일을 Figma로 변환해서 마크업 작업 준비해줘"
**트리거:** 피그마, Figma, 피그마 마크업, 피그마 HTML, 디자인 시안 마크업, Figma to HTML
피그마 디자인 시안을 HTML 마크업으로 변환하는 데 특화된 Skill입니다. markup Skill을 기반으로 피그마 레이어 구조 해석, 컴포넌트 매핑, 반응형 브레이크포인트 추출을 포함합니다.
"이 피그마 디자인 시안을 HTML 마크업으로 변환해줘"
### HTML 시멘틱 태그 가이드
| 영역 | 시멘틱 태그 | 규칙 |
| --- | --- | --- |
| **레이아웃** | <header>, <nav>, <main>, <aside>, <footer> | 페이지 대구조 정의, div 남용 금지 |
| **섹션** | <section>, <article> | 의미 단위 그룹핑, aria-label 필수 |
| **제목** | <h1>~<h6> | 페이지당 h1 1개, 건너뛰기 금지 |
| **목록** | <ul>, <ol>, <dl> | 나열형 데이터는 반드시 리스트 태그 |
| **폼** | <form>, <fieldset>, <label> | 모든 input에 label 연결 필수 |
| **인터랙티브** | <button>, <a>, <dialog> | 클릭 가능 요소는 반드시 시멘틱 태그 |
### Tailwind 클래스 순서 컨벤션
| 순서 | 카테고리 | 대표 클래스 | 기억법 |
| --- | --- | --- | --- |
| **1** | Layout | flex, grid, block, hidden | "어떻게 배치?" |
| **2** | Position | relative, absolute, fixed, z-\* | "어디에 놓을까?" |
| **3** | Box Model | w-\*, h-\*, p-\*, m-\*, gap-\* | "얼마나 크게?" |
| **4** | Typography | text-\*, font-\*, leading-\* | "글자를 어떻게?" |
| **5** | Visual | bg-\*, border-\*, rounded-\*, shadow-\* | "어떻게 보이게?" |
| **6** | State | hover:\*, focus:\*, sm:\*, md:\* | "상태가 바뀌면?" |
| **7** | Motion | transition-\*, duration-\*, animate-\* | "움직임은?" |
**트리거:** 프로모션, 랜딩페이지, 이벤트 페이지, 프로모션 마크업, 캠페인 페이지
프로모션 및 랜딩페이지에 특화된 HTML 마크업을 생성합니다. 캠페인 성격에 맞는 시멘틱 구조, 반응형 레이아웃, CTA 배치 패턴을 포함합니다.
"이 프로모션 디자인을 랜딩페이지 HTML 마크업으로 변환해줘"
**트리거:** EDM, 이메일, 뉴스레터, 메일 템플릿, 이메일 마크업
EDM(Email Direct Marketing) 및 뉴스레터용 HTML 마크업을 생성합니다. 이메일 클라이언트 호환성, 테이블 기반 레이아웃, 인라인 스타일 패턴을 포함합니다.
"이 디자인을 이메일 뉴스레터 HTML로 변환해줘"
---
**트리거:** Nuxt 컴포넌트, 페이지, composable, 레이아웃, 미들웨어, 플러그인
Nuxt 3 공식 가이드를 기반으로 컴포넌트를 설계합니다. auto-import, composables 패턴, server/ 디렉토리 활용, 레이아웃 전략을 포함한 전체 아키텍처를 제안합니다.
"이 기능을 Nuxt 3 Best Practice에 맞게 컴포넌트로 설계해줘"
### Phase 3-1 — Nuxt 공식문서 참조 구조 (references/)
| 참조 파일 | Nuxt 공홈 출처 | 로드 트리거 |
| --- | --- | --- |
| `server.md` | API routes, server middleware | 서버 API, 미들웨어 작업 시 |
| `middleware.md` | Middleware, File-based routing | 미들웨어, 라우팅 설계 시 |
| `plugins.md` | Plugins, app lifecycle | 플러그인, 앱 라이프사이클 작업 시 |
| `nuxt-composables.md` | Nuxt composables | composable 작성 시 |
| `nuxt-components.md` | NuxtLink, NuxtImg, NuxtTime | Nuxt 내장 컴포넌트 사용 시 |
| `nuxt-config.md` | Configuration, modules, auto-imports, layers | Nuxt 설정, 모듈 구성 시 |
**트리거:** Nuxt 공식문서, Nuxt 가이드, Nuxt API, NuxtLink, NuxtImg, definePageMeta, useFetch, server/api
Nuxt 공식문서를 기반으로 Best Practice에 맞는 코드를 생성합니다. 6개 참조 문서(server, routing, middleware-plugins, composables, components, config)를 컨텍스트에 따라 선택적으로 로드합니다.
"Nuxt 공식 가이드 기반으로 이 기능을 구현해줘"
### 디렉토리 구조 표준
project structure project-root/ ├── app.vue # 루트 컴포넌트 ├── nuxt.config.ts # Nuxt 설정 ├── pages/ # 파일 기반 라우팅 ├── components/ # Auto-import 컴포넌트 │ ├── base/ # Atoms (BaseButton, BaseInput) │ ├── common/ # Molecules (SearchBar, Card) │ └── domain/ # Organisms (ProductList) ├── composables/ # Auto-import 로직 ├── layouts/ # 레이아웃 템플릿 ├── middleware/ # 라우트 미들웨어 ├── plugins/ # 플러그인 ├── server/ # 서버 API (Nitro) ├── stores/ # Pinia 스토어 ├── types/ # TypeScript 타입 └── utils/ # 유틸리티 함수
### Phase 3-2 — 페이지·레이아웃·미들웨어 생성
**트리거:** 페이지 만들어줘, 레이아웃 추가, 미들웨어 작성, 라우트 추가, 새 페이지, page, layout, middleware
Nuxt 3의 파일 기반 라우팅 체계에 맞춰 페이지, 레이아웃, 미들웨어를 생성합니다. `definePageMeta`로 layout·middleware를 연결하고, `useSeoMeta`로 SEO 메타태그를 설정합니다.
"새 페이지를 만들고 레이아웃과 인증 미들웨어를 연결해줘"
#### Skill 동작 절차
1. **기존 구조 탐색**`pages/`, `layouts/`, `middleware/` 디렉토리 확인
2. **라우트 구조 결정** — 파일 기반 라우팅 경로 확인, 동적 파라미터(`[id].vue`), catch-all(`[...slug].vue`)
3. **레이아웃 선택/생성** — 기존 레이아웃 재사용 우선, 필요 시 신규 생성
4. **페이지 생성**`<script setup lang="ts">`, `definePageMeta`, `useSeoMeta` 적용
5. **미들웨어 추가** — Named middleware(`middleware/auth.ts`) 또는 인라인 방식
6. **검증** — TypeScript 오류 확인, 라우트 인식 확인
#### 동적 라우트 패턴
| 파일명 | 라우트 | 예시 URL |
| --- | --- | --- |
| `pages/users/[id].vue` | `/users/:id` | `/users/123` |
| `pages/posts/[...slug].vue` | `/posts/:slug(.*)` | `/posts/2026/04/hello` |
| `pages/[[optional]].vue` | `/:optional?` | `/` 또는 `/about` |
⚠️ `definePageMeta`에는 정적/직렬화 가능한 값만 허용. 런타임 표현식 사용 불가.
### Phase 3-3 — Vue 컴포넌트 신규 생성
**트리거:** 컴포넌트 만들어줘, 새 컴포넌트, UI 컴포넌트 추가, 버튼 만들어줘, 카드 컴포넌트
팀 컨벤션에 맞는 새로운 Vue 3 컴포넌트를 생성합니다. `vue-component-review` skill과의 **역할 구분**: review는 기존 코드 점검, create는 새 파일 생성입니다.
"사용자 프로필 카드 컴포넌트를 만들어줘"
#### Skill 동작 절차
1. **요구사항 확인** — 컴포넌트 목적, props, emits, 사용 위치 파악
2. **기존 컴포넌트 탐색**`components/` 에서 유사 컴포넌트 확인, 재활용 검토
3. **파일 위치 결정**`base/` (Atoms), `common/` (Molecules), `domain/` (Organisms)
4. **인터페이스 설계**`defineProps<T>()`, `defineEmits<{}>()` 제네릭 형태
5. **구현**`<script setup lang="ts">`, Tailwind 유틸리티 클래스
6. **검증** — TypeScript 오류, 200줄 이내 확인
#### 분리 기준
| 상황 | 조치 |
| --- | --- |
| 파일 200줄 초과 | 하위 컴포넌트로 분리 |
| script 로직 30줄 초과 | composable로 추출 |
| Props 5개 초과 | 객체 prop으로 그룹핑 |
| 동일 패턴 3회 이상 반복 | 공통 컴포넌트로 추출 |
### Phase 3-4 — Composable·데이터 페칭
**트리거:** composable 만들어줘, useFetch 패턴, 데이터 페칭, useAsyncData, 커스텀 훅, 로직 추출
Nuxt 3의 composable 함수와 데이터 페칭 패턴을 생성합니다. 데이터 페칭 composable(서버 상태)과 로직 composable(UI 상태/행동)을 구분합니다.
"유저 프로필 데이터를 가져오는 composable을 만들어줘"
#### 데이터 페칭 판단 기준표
| 시나리오 | 추천 방식 | 이유 |
| --- | --- | --- |
| 단순 REST GET | `useFetch` | 자동 key 중복 방지, 간결 |
| 커스텀 key·transform 필요 | `useAsyncData` | 캐싱/변환 세밀 제어 |
| 이벤트 핸들러 내 POST/PUT/DELETE | `$fetch` | SSR 불필요, fire-and-forget |
| 의존 쿼리 (체이닝) | `useAsyncData` + `watch` | 실행 순서 제어 |
⚠️ `$fetch``<script setup>`에서 직접 사용하면 SSR 시 서버/클라이언트 양쪽에서 실행되어 이중 요청 발생. 반드시 `useFetch` 또는 `useAsyncData`로 감싸야 합니다.
---
**트리거:** API 연동, useFetch, useAsyncData, Pinia, 스토어, 상태관리, 에러 핸들링
Nuxt 3의 데이터 페칭 유틸리티와 Pinia 스토어를 조합한 표준 패턴을 생성합니다.
"이 API를 Nuxt에서 연동하고 Pinia 스토어로 상태관리해줘"
### 데이터 페칭 패턴 가이드
| 패턴 | 사용 시점 | 특징 |
| --- | --- | --- |
| **`useFetch()`** | 컴포넌트에서 직접 API 호출 | SSR 지원, 자동 캐싱, key 기반 중복 방지 |
| **`useAsyncData()`** | 복잡한 데이터 변환 필요 시 | 커스텀 fetch 로직, transform 지원 |
| **`$fetch()`** | 이벤트 핸들러 내 API 호출 | 클라이언트 사이드만, SSR 미지원 |
| **`server/api/`** | BFF 패턴, 서버 사이드 로직 | Nitro 기반, API 키 보호 |
### Pinia 스토어 설계 규칙
- Setup Store 문법 사용 (Option Store 대비 composable 호환성 우수)
- 스토어 네이밍: use\[Domain\]Store (useAuthStore, useCartStore)
- 상태 최소화 원칙: 파생 가능한 값은 computed로 처리
- Action에서 API 호출 금지: composable(useApi)에서 호출 후 결과만 저장
- 스토어 간 의존성 최소화: 순환 참조 금지
### Phase 4-1 — Pinia 스토어 생성
**트리거:** 스토어 만들어줘, Pinia 스토어, 전역 상태, store 추가, 상태관리, useAuthStore, useCartStore
앱 전역에서 공유하는 상태를 Pinia 스토어로 관리합니다. **Setup Store(Composition API) 문법**을 기본으로 사용하며, 서버 데이터는 composable(`useFetch`)이 담당합니다.
"인증 상태를 관리하는 Pinia 스토어를 만들어줘"
#### Skill 동작 절차
1. **스토어 필요성 판단** — 서버 데이터 → composable, 로컬 상태 → ref, 공유 상태 → 스토어
2. **기존 스토어 탐색**`stores/` 디렉토리 확인, 중복 방지
3. **상태 설계** — TypeScript 인터페이스, 최소한의 원시 상태만 저장
4. **Setup Store 구현** — state(`ref`), getters(`computed`), actions(함수)
5. **검증** — TypeScript 오류, SSR hydration 호환성
#### 스토어 필요성 판단 가이드
| 상태의 성격 | 해결 방법 | 예시 |
| --- | --- | --- |
| 서버 데이터 (API 응답) | composable + `useFetch` | 상품 목록, 유저 프로필 |
| 앱 전역 공유 상태 | **Pinia 스토어** | 인증, 테마, 사이드바 |
| 단일 페이지 폼 상태 | 로컬 `ref`/`reactive` | 회원가입 폼 |
| 부모↔자식 공유 상태 | props/emits, `provide/inject` | 아코디언 그룹 |
| URL 기반 상태 | 라우트 쿼리/파라미터 | 필터, 정렬, 페이지 |
⚠️ SSR hydration 주의: 스토어 상태는 HTML로 직렬화됨. 함수, DOM 참조, 클래스 인스턴스 등 직렬화 불가능한 값 저장 금지.
### Phase 4-2 — Nitro 서버 라우트 (BFF)
**트리거:** API 라우트 만들어줘, 서버 라우트, server/api, BFF, 프록시 API, 서버 미들웨어, Nitro, defineEventHandler
Nuxt 3의 Nitro 서버 라우트를 생성합니다. BFF 프록시 패턴, API 엔드포인트, 서버 미들웨어, 서버 유틸리티를 포함합니다.
"유저 조회 API 라우트를 BFF 프록시 패턴으로 만들어줘"
#### Skill 동작 절차
1. **라우트 목적 파악** — BFF 프록시, 데이터 변환, 인증, 서버 전용 로직
2. **기존 서버 라우트 탐색**`server/api/`, `server/utils/`, `server/middleware/` 확인
3. **HTTP 메서드 및 파일 네이밍**`xxx.get.ts`, `xxx.post.ts`, `xxx.delete.ts`
4. **요청/응답 타입 정의**`readBody<T>()`, `getQuery()`, `getRouterParam()`
5. **핸들러 구현**`defineEventHandler`, 입력 검증, `createError` 에러 처리
6. **클라이언트 연결** — composable에서 `useFetch('/api/xxx')` 연결 안내
#### 서버 디렉토리 구조
```
server/
├── api/
│ ├── auth/
│ │ ├── login.post.ts
│ │ └── logout.post.ts
│ └── users/
│ ├── index.get.ts # GET /api/users
│ ├── index.post.ts # POST /api/users
│ └── [id].get.ts # GET /api/users/:id
├── middleware/
│ └── auth.ts # 모든 요청에 적용
└── utils/
└── api-client.ts # 공유 유틸리티
```
⚠️ 비밀키는 `useRuntimeConfig()` 서버 전용 필드로만 접근. 클라이언트에 노출 금지. 서버 라우트는 Nitro(Node.js) 컨텍스트이므로 Vue 반응성(`ref`, `computed`)과 브라우저 API 사용 불가.
---
Phase 5는 **기획 요구사항 검증(5-1)****성능 최적화(5-2)** 를 통합한 검증 단계입니다. Phase 1에서 정의한 요구사항 대비 실제 구현 결과를 비교 검증하고, 런타임 퍼포먼스를 최적화합니다.
### Phase 5-1 — 기획 요구사항 검증
**트리거:** 요구사항 검증, 기획 대비 구현 비교, 기능 누락 확인, 스펙 검증, 구현 완성도
Phase 1에서 정의한 요구사항 명세와 실제 구현 결과를 비교 검증합니다. 기능 누락, 스펙 불일치, 미구현 항목을 자동으로 식별하고 개선 방안을 제시합니다.
"기획서 대비 현재 구현 상태를 검증하고 누락된 기능을 찾아줘"
### Phase 5-2 — 성능 최적화 (CWV)
**트리거:** 성능, 최적화, Lighthouse, Core Web Vitals, LCP, CLS, INP, 번들, 로딩 속도, 캐싱, lazy loading
Nuxt 3 프로젝트의 성능 병목을 분석하고 최적화합니다. 코드 스플리팅, 이미지 최적화, SSR/ISR 렌더링 전략, API 캐싱, Payload 최적화를 포함합니다.
"이 페이지 LCP가 3초 넘는데 성능 최적화 방안 제시해줘"
### 성능 최적화 체크리스트
| 카테고리 | 최적화 항목 | Nuxt 3 적용 방법 |
| --- | --- | --- |
| **번들** | 코드 스플리팅 | defineAsyncComponent, lazy 접두사 컴포넌트 |
| **번들** | 트리 쉐이킹 | nuxt.config의 build.transpile, 사이드이펙트 제거 |
| **이미지** | NuxtImg | <NuxtImg> sizes/format 자동 최적화 |
| **이미지** | Lazy Loading | loading="lazy", NuxtImg placeholder |
| **네트워크** | API 캐싱 | useFetch의 key, getCachedData |
| **네트워크** | 서버 캐싱 | routeRules의 swr, isr 설정 |
| **렌더링** | SSR/SSG 전략 | routeRules로 페이지별 렌더링 모드 지정 |
| **렌더링** | Payload 최적화 | useAsyncData의 transform으로 불필요 데이터 제거 |
| **CWV** | LCP < 2.5s | critical CSS 인라인, 폰트 프리로드, hero 이미지 우선 |
| **CWV** | CLS < 0.1 | 이미지 width/height 명시, 스켈레톤 UI, font-display: swap |
| **CWV** | INP < 200ms | 이벤트 핸들러 최적화, 메인 스레드 블로킹 제거 |
💬 프롬프트 템플릿
```
이 Nuxt 3 프로젝트의 성능을 감사하고 최적화 방안을 제시해줘.
[프로젝트 경로 또는 주요 파일]
감사 범위:
- Core Web Vitals (LCP < 2.5s, CLS < 0.1, INP < 200ms)
- 번들 사이즈 분석 (analyze: true로 리포트 확인)
- 이미지 최적화 상태 (NuxtImg 활용 여부)
- SSR/SSG/ISR 렌더링 전략 적정성
- routeRules 캐싱 전략 검증
- 불필요한 클라이언트 JS hydration 확인
```
---
2026년 현재, 전통적 SEO만으로는 디지털 가시성을 확보할 없습니다. Gartner는 전통 검색 볼륨이 25% 감소할 것으로 전망하며, ChatGPT·Perplexity·Google AI Overview가 새로운 검색 채널로 부상하고 있습니다. Skill은 **SEO + GEO + AEO 3계층 최적화** 통합합니다.
### SEO · GEO · AEO 3계층 이해
**🔍 Layer 1 SEO (Search Engine Optimization)**
전통 검색엔진(Google, Bing) 결과 페이지에서 상위 노출. 메타태그, 구조화 데이터, 사이트맵, 기술적 SEO(Core Web Vitals, crawlability) 핵심.
**💬 Layer 2 AEO (Answer Engine Optimization)**
Featured Snippet, Google AI Overview, 음성 검색 "직접 답변" 영역에 콘텐츠를 노출. FAQ 구조, 질문형 H2 헤딩, 간결한 정의 문장이 핵심.
**🤖 Layer 3 GEO (Generative Engine Optimization)**
ChatGPT, Perplexity, Claude, Gemini AI 생성형 답변에서 콘텐츠가 인용(citation)되도록 최적화. 통계 데이터, 전문가 인용, 구조화된 정보 블록, 원본 리서치가 핵심.
💡
**3계층은 상호 보완적입니다.** 탄탄한 SEO 기반 위에 AEO 구조를 얹고, GEO 최적화를 추가하는 것이 가장 효과적입니다. Princeton 연구에 따르면 GEO 최적화 기법(통계 인용, 출처 명시, 전문가 인용문) 적용하면 AI 검색 가시성이 30~40% 향상됩니다.
**트리거:** SEO, 메타태그, 구조화 데이터, Schema.org, sitemap, OG태그, GEO, AI 검색, 인용 최적화, AEO, Featured Snippet, FAQ, 음성 검색, useSeoMeta, useSchemaOrg
Nuxt 3 프로젝트의 SEO·GEO·AEO를 3계층으로 감사하고 최적화합니다. 메타태그 자동화, JSON-LD 구조화 데이터, AI 인용 친화적 콘텐츠 구조, FAQ 스키마, OG 이미지 자동 생성을 포함합니다.
" 사이트의 SEO 메타태그, GEO 인용 최적화, AEO FAQ 구조를 검증해줘"
### Layer 1 — SEO 체크리스트
| 영역 | 점검 항목 | Nuxt 3 구현 |
| --- | --- | --- |
| **메타태그** | title, description, canonical, OG, Twitter Card | useSeoMeta() 전역 + 페이지별 오버라이드 |
| **구조화 데이터** | JSON-LD Schema.org (Article, Product, FAQ ) | @nuxtjs/schema-org 모듈, useSchemaOrg() |
| **사이트맵** | 동적 sitemap.xml 생성 | @nuxtjs/sitemap 모듈 |
| **robots** | robots.txt + 크롤 제어 | @nuxtjs/robots 모듈 또는 server/routes |
| **OG 이미지** | 페이지별 동적 OG 이미지 | nuxt-og-image 또는 Satori 기반 server/api |
| **Canonical** | 중복 URL 방지 | useHead({ link: \[{ rel: 'canonical' }\] }) |
| **SSR** | 검색엔진 크롤러 SSR 보장 | routeRules로 SSR/ISR 모드 지정 |
| **i18n** | hreflang 태그, lang 속성 | @nuxtjs/i18n 모듈 |
### Layer 2 — AEO 체크리스트
| 영역 | 점검 항목 | 구현 전략 |
| --- | --- | --- |
| **질문형 구조** | H2를 실제 사용자 질문 형태로 | "제품 가격은?" 대신 " 제품의 가격은 얼마인가요?" |
| **즉답 패턴** | 질문 직후 200자 이내 직접 답변 | TLDR-first 콘텐츠 구조 적용 |
| **FAQ Schema** | FAQPage JSON-LD | useSchemaOrg(\[defineFAQPage(...)\]) |
| **HowTo Schema** | 단계별 가이드 구조화 | useSchemaOrg(\[defineHowTo(...)\]) |
| **Breadcrumb** | 탐색 경로 구조화 | useSchemaOrg(\[defineBreadcrumb(...)\]) |
| **정의 문장** | 핵심 용어의 명확한 정의 문장 | "\[용어\] \[정의\]입니다" 패턴으로 시작 |
### Layer 3 — GEO 체크리스트
| 영역 | 점검 항목 | 최적화 전략 |
| --- | --- | --- |
| **통계 인용** | 구체적 수치 데이터 포함 | "매출 20% 증가" AI가 인용할 확률 |
| **출처 명시** | 데이터·인사이트의 원본 출처 표기 | 신뢰도 향상 AI 엔진이 선호 |
| **전문가 인용** | 업계 전문가의 직접 인용문 | blockquote + 전문가 Schema 활용 |
| **추출 가능한 블록** | 독립적으로 의미가 완결되는 정보 단위 | 섹션이 독립적으로 인용 가능하도록 구성 |
| **E-E-A-T 신호** | 경험(Experience), 전문성, 권위, 신뢰 | 저자 프로필, 리뷰 날짜, 인증 배지 |
| **최신성 유지** | 정기적 콘텐츠 업데이트 | AI 인용 콘텐츠의 50% 13주 이내 게시된 |
| **토픽 중심** | 키워드 토픽 타겟팅 전환 | 주제를 포괄적으로 다루는 pillar 콘텐츠 |
| **브랜드 언급** | 전체에서 브랜드 언급 확대 | 외부 인용, 리뷰, 포럼 활동 |
### SEO 메타 자동화 패턴
💬 useSeoMeta + useSchemaOrg 통합 패턴
```
// composables/usePageSeo.ts — SEO·GEO 통합 메타 자동화
```
### GEO·AEO 감사 프롬프트
💬 SEO + GEO + AEO 통합 감사 프롬프트
```
이 Nuxt 3 프로젝트의 SEO·GEO·AEO를 3계층으로 감사해줘.
[프로젝트 경로 또는 주요 파일]
Layer 1 — SEO 감사:
- useSeoMeta 적용 완성도 (title, description, OG, canonical)
- JSON-LD 구조화 데이터 (Schema.org 타입 적정성)
- sitemap.xml, robots.txt 설정
- OG 이미지 동적 생성 여부
Layer 2 — AEO 감사:
- H2 헤딩이 질문형으로 작성되었는가
- 각 섹션이 질문 직후 즉답(200자 이내)을 제공하는가
- FAQPage, HowTo, Breadcrumb 스키마 적용 여부
Layer 3 — GEO 감사:
- 통계·수치 데이터가 출처와 함께 포함되었는가
- 각 콘텐츠 블록이 독립적으로 AI에 인용 가능한 구조인가
- 저자(Author) 정보와 E-E-A-T 신호가 충분한가
- dateModified가 최신 상태인가
```
---
### 공식 Skills 폴더 구조 가이드 🔗 공식문서
Claude Code 공식문서에 따르면, Skill을 저장하는 **위치에 따라 적용 범위** 결정됩니다.
우선순위: Enterprise > Personal > Project 순이며, Plugin Skills는 네임스페이스(`plugin-name:skill-name`)를 사용하므로 충돌하지 않습니다.
| 위치 | 경로 | 적용 대상 |
| --- | --- | --- |
| **Enterprise** | 관리 설정(Managed Settings) 참조 | 조직의 모든 사용자 |
| **Personal** | `~/.claude/skills/<skill-name>/SKILL.md` | 모든 프로젝트에서 사용 가능 |
| **Project** | `.claude/skills/<skill-name>/SKILL.md` | 해당 프로젝트에서만 사용 |
| **Plugin** | `<plugin>/skills/<skill-name>/SKILL.md` | 플러그인이 활성화된 위치 |
#### 공식 Skill 디렉토리 구조
각 Skill은 `SKILL.md` 를 진입점으로 하는 디렉토리입니다. `SKILL.md` 만 필수이며, 나머지 파일은 선택적입니다.
official structure my-skill/ ├── SKILL.md # 주요 지침 (필수) ├── template.md # Claude가 채울 템플릿 ├── examples/ │ └── sample.md # 예상 형식을 보여주는 예제 출력 └── scripts/ └── validate.sh # Claude가 실행할 수 있는 스크립트
#### 공식 네이밍 규칙
| 대상 | 규칙 | 비고 |
| --- | --- | --- |
| **name 필드** | 소문자, 숫자, 하이픈만 사용 (최대 64자) | `/slash-command` 로 호출 시 사용 |
| **폴더명** | name 미지정 시 디렉토리 이름이 기본 name | kebab-case 권장 |
| **SKILL.md** | 대문자 고정, 폴더당 1개 (필수) | 500줄 이내 권장 |
| **지원 파일** | SKILL.md에서 참조하여 필요 시 로드 | 상세 내용은 references/로 분리 |
📖
**공식문서 참조:** `.claude/commands/` 의 기존 파일도 계속 작동하며 동일한 frontmatter를 지원합니다. 단, Skills가 지원 파일 등 추가 기능을 제공하므로 **Skills 형식이 권장** 됩니다. Skill과 명령어가 같은 이름을 공유하면 Skill이 우선합니다.
### 전체 Skills 폴더 구조 (프로젝트 적용)
skill tree · v1.2 nuxt-fe-skills/ │ ├── requirement-analyzer/ # Phase 1: 요구사항 분석 │ ├── SKILL.md │ ├── scripts/ │ │ └── generate-flowchart.js │ └── references/ │ └── requirement-template.md │ ├── markup/ # Phase 2: 마크업 컨벤션 │ ├── SKILL.md │ ├── references/ │ │ ├── semantic-html.md │ │ ├── tailwind-convention.md │ │ ├── accessibility.md │ │ └── nuxt-sfc-pattern.md ├── markup-promotion/ # Phase 2-2: 프로모션 마크업 🆕 v1.1 │ ├── SKILL.md │ ├── references/ ├── markup-edm/ # Phase 2-3: EDM 마크업 🆕 v1.1 │ ├── SKILL.md │ ├── references/ │ ├── nuxt-component-architect/ # Phase 3: 컴포넌트 아키텍처 │ └── SKILL.md │ ├── nuxt/ # Phase 3-1: Nuxt 공식문서 기반 🆕 v1.1 │ ├── SKILL.md │ └── references/ │ ├── server.md │ ├── middleware.md │ ├── plugins.md │ ├── nuxt-composables.md │ ├── nuxt-components.md │ └── nuxt-config.md │ ├── nuxt-page-layout/ # Phase 3-2: 페이지·레이아웃·미들웨어 🆕 v1.2 │ └── SKILL.md │ ├── vue-component-create/ # Phase 3-3: Vue 컴포넌트 신규 생성 🆕 v1.2 │ └── SKILL.md │ ├── nuxt-composable/ # Phase 3-4: Composable·데이터 페칭 🆕 v1.2 │ └── SKILL.md │ ├── nuxt-api-state-manager/ # Phase 4: API·상태관리 │ ├── SKILL.md │ ├── scripts/ │ │ └── generate-store.js │ └── references/ │ ├── pinia-patterns.md │ └── api-error-handling.md │ ├── pinia-store/ # Phase 4-1: Pinia 스토어 생성 🆕 v1.2 │ └── SKILL.md │ ├── nuxt-api-route/ # Phase 4-2: Nitro 서버 라우트(BFF) 🆕 v1.2 │ └── SKILL.md │ ├── requirement-optimizer/ # Phase 5-1: 요구사항 검증 🆕 v1.1 │ ├── SKILL.md │ └── references/ │ └── requirement-checklist.md │ ├── perf-optimizer/ # Phase 5-2: 성능 최적화 ⚡ v1.1 │ ├── SKILL.md │ ├── scripts/ │ │ └── lighthouse-runner.js # Lighthouse CI 자동 실행 │ └── references/ │ ├── cwv-checklist.md # Core Web Vitals 체크리스트 │ ├── bundle-optimization.md # 번들 최적화 전략 │ ├── image-optimization.md # NuxtImg 활용 가이드 │ └── caching-strategy.md # routeRules 캐싱 가이드 │ └── nuxt-seo-geo/ # Phase 6: SEO·GEO·AEO 🆕 v1.1 ├── SKILL.md ├── scripts/ │ ├── seo-meta-checker.js # 메타태그 완성도 자동 검사 │ └── schema-validator.js # JSON-LD 구조화 데이터 검증 └── references/ ├── seo-meta-guide.md # useSeoMeta 패턴 가이드 ├── schema-org-guide.md # useSchemaOrg JSON-LD 가이드 ├── aeo-patterns.md # AEO: FAQ, HowTo, 즉답 패턴 ├── geo-optimization.md # GEO: AI 인용 최적화 전략 └── og-image-generation.md # 동적 OG 이미지 자동 생성
### Skill 설계 3원칙
📐
**원칙 1: Progressive Disclosure (점진적 공개)** — SKILL.md는 500줄 이내. 상세 내용은 references/에 분리. Claude는 컨텍스트에 따라 필요한 참조만 선택적으로 로드합니다.
🎯
**원칙 2: Pushy Description (적극적 트리거)** — Skill description에 다양한 트리거 키워드를 포함하여 관련 상황에서 누락 없이 활성화. 언더트리거보다 오버트리거가 낫습니다.
📋
**원칙 3: Deterministic Output (결정적 출력)** — 동일한 입력에 대해 일관된 구조의 출력을 보장. 출력 포맷(디렉토리 구조, 코드 패턴, 체크리스트)을 명확히 정의합니다.
---
### 도입 타임라인
| 주차 | Phase | 핵심 활동 | 산출물 |
| --- | --- | --- | --- |
| **W1W2** | Phase 1 | 요구사항 분석 Skill 개발 및 테스트 | `requirement-analyzer` |
| **W2W3** | Phase 2 / 2-0 / 2-1 / 2-2 / 2-3 | 마크업 컨벤션 + PSD→Figma·피그마 마크업·프로모션·EDM Skill 개발 | `markup`, `markup-promotion`, `markup-edm` |
| **W3W5** | Phase 3 / 3-1 / 3-2 / 3-3 / 3-4 | Nuxt 컴포넌트 아키텍처 + 공식문서 참조 + 페이지·컴포넌트·Composable Skill 개발 | `nuxt-component-architect`, `nuxt`, `nuxt-page-layout`, `vue-component-create`, `nuxt-composable` |
| **W5W7** | Phase 4 / 4-1 / 4-2 | API/Pinia 패턴 표준화 + Pinia 스토어 + Nitro 서버 라우트 Skill 개발 | `nuxt-api-state-manager`, `pinia-store`, `nuxt-api-route` |
| **W7W8** | Phase 5 검증 (5-1) | 요구사항 검증 Skill 개발 | `requirement-optimizer` NEW |
| **W8W9** | Phase 5-2 | 성능 최적화 Skill 개발 및 파일럿 | `perf-optimizer` CHANGED |
| **W9W10** | Phase 6 NEW | SEO·GEO·AEO 감사 Skill 개발 | `nuxt-seo-geo` |
| **W11W12** | 통합 | 전체 Skill 통합 테스트 및 팀 교육 | 교육 자료, 온보딩 가이드 |
| **W13** | 운영 | 실 프로젝트 적용 및 피드백 기반 개선 | Skill 버전 업데이트 로그 |
### 성공 지표 (KPI)
코드 리뷰 반려율
35%
15%↓
컴포넌트 재사용률
30%
70%↑
신규 온보딩 기간
3주
1주
Lighthouse 성능 점수
65~75
90+
SEO 메타 누락률
40%
5%↓
AI 검색 인용률 NEW
미측정
측정 체계 구축
---
본 문서는 퍼블리싱웹 FE 내부 공유용이며, 프로젝트 진행에 따라 지속 업데이트됩니다.
문의: 퍼블리싱웹 FE · 최종 수정: 2026.04.23 · v1.2

View File

@@ -0,0 +1,147 @@
---
name: api-pinia-store
description: Pinia 스토어를 팀 컨벤션에 맞게 생성할 때 사용합니다. "스토어 만들어줘", "Pinia 스토어", "전역 상태", "store 추가", "상태관리", "useAuthStore", "useCartStore" 등을 요청하면 트리거됩니다.
---
# Pinia 스토어 생성
이 skill은 Pinia 스토어를 팀 컨벤션에 맞게 생성합니다.
**Setup Store(Composition API) 문법**을 기본으로 사용합니다.
## 작업 순서
1. **스토어 필요성 판단**
- 아래 판단 가이드를 참고하여 스토어가 적합한지 확인
- 서버 데이터 → composable(`useFetch`), 로컬 상태 → `ref`/`reactive` 권장
2. **기존 스토어 탐색**
- `stores/` 디렉토리에서 기존 스토어 패턴 확인
- 중복 생성 방지, 기존 스토어 확장 가능 여부 검토
3. **상태 설계**
- TypeScript 인터페이스로 상태 형태 정의
- 최소한의 원시 상태만 저장, 파생 가능한 값은 `computed`
4. **Setup Store 구현**
- state → `ref`/`reactive`
- getters → `computed`
- actions → 일반 함수
- 모두 하나의 객체로 반환
5. **persistence 추가 (필요 시)**
- `pinia-plugin-persistedstate` 사용 여부 사용자 확인
6. **검증**
- TypeScript 오류 확인
- SSR hydration 호환성 확인
## 스토어 필요성 판단 가이드
| 상태의 성격 | 해결 방법 | 예시 |
| --- | --- | --- |
| 서버 데이터 (API 응답) | composable + `useFetch` | 상품 목록, 유저 프로필 |
| 앱 전역 공유 상태 | **Pinia 스토어** | 인증, 테마, 사이드바 상태 |
| 단일 페이지 폼 상태 | 로컬 `ref`/`reactive` | 회원가입 폼 |
| 부모↔자식 공유 상태 | props/emits 또는 `provide/inject` | 아코디언 그룹 |
| URL 기반 상태 | 라우트 쿼리/파라미터 | 필터, 정렬, 페이지 |
## Setup Store 템플릿
```ts
// stores/useAuthStore.ts
import type { User } from '~/types/user';
export const useAuthStore = defineStore('auth', () => {
// state
const user = ref<User | null>(null);
const token = ref<string | null>(null);
// getters
const isLoggedIn = computed(() => !!token.value);
const userName = computed(() => user.value?.name ?? '');
// actions
async function login(credentials: LoginCredentials) {
const response = await $fetch<AuthResponse>('/api/auth/login', {
method: 'POST',
body: credentials,
});
user.value = response.user;
token.value = response.token;
}
function logout() {
user.value = null;
token.value = null;
navigateTo('/login');
}
return {
// state
user,
token,
// getters
isLoggedIn,
userName,
// actions
login,
logout,
};
});
```
## UI 상태 스토어 템플릿
```ts
// stores/useUiStore.ts
export const useUiStore = defineStore('ui', () => {
const isSidebarOpen = ref(false);
const theme = ref<'light' | 'dark'>('light');
function toggleSidebar() {
isSidebarOpen.value = !isSidebarOpen.value;
}
function setTheme(newTheme: 'light' | 'dark') {
theme.value = newTheme;
}
return {
isSidebarOpen,
theme,
toggleSidebar,
setTheme,
};
});
```
## Persistence 적용 패턴
```ts
// stores/useAuthStore.ts
export const useAuthStore = defineStore('auth', () => {
const token = ref<string | null>(null);
// ... 생략
return { token };
}, {
persist: {
pick: ['token'],
},
});
```
## 스토어 네이밍 규칙
- 파일: `stores/use[Domain]Store.ts`
- 함수: `use[Domain]Store`
- defineStore ID: 소문자 도메인명 (예: `'auth'`, `'cart'`, `'ui'`)
## 주의사항
- **Option Store 대신 Setup Store 사용**: `<script setup>` 패턴과의 일관성
- **서버 데이터를 Pinia에 저장하지 않음**: `useFetch`/`useAsyncData` 영역
- **SSR hydration**: 스토어 상태는 HTML로 직렬화됨. 함수, DOM 참조, 클래스 인스턴스 등 직렬화 불가능한 값 저장 금지
- **스토어 간 순환 참조 금지**: A → B → A 형태의 의존성 제거
- **대형 모놀리식 스토어 지양**: 도메인별 분리 (auth, ui, cart 등)
- **Action 내 직접 API 호출 주의**: 프로젝트 규칙에 따라 composable에서 호출 후 결과만 저장하는 패턴 확인
- `any` 타입 사용 금지 (`rules/coding-conventions.md` 참조)

View File

@@ -0,0 +1,194 @@
---
name: api-server-route
description: Nuxt 3 서버 라우트(Nitro)와 API 통합 레이어를 작성할 때 사용합니다. "API 라우트 만들어줘", "서버 라우트", "server/api", "BFF", "프록시 API", "서버 미들웨어", "Nitro", "defineEventHandler" 등을 요청하면 트리거됩니다.
---
# Nuxt 서버 라우트 (Nitro)
이 skill은 Nuxt 3의 Nitro 서버 라우트를 생성합니다.
BFF 프록시 패턴, API 엔드포인트, 서버 미들웨어, 서버 유틸리티를 포함합니다.
## 작업 순서
1. **라우트 목적 파악**
- BFF 프록시 (외부 API 중계)
- 내부 데이터 변환/가공
- 인증 엔드포인트
- 서버 전용 로직
2. **기존 서버 라우트 탐색**
- `server/api/` 구조 확인
- `server/utils/` 에서 공유 유틸리티 확인
- `server/middleware/` 에서 기존 미들웨어 확인
3. **HTTP 메서드 및 파일 네이밍 결정**
- 메서드별 파일: `xxx.get.ts`, `xxx.post.ts`, `xxx.delete.ts`
- 통합 파일: `xxx.ts` (모든 메서드 처리)
4. **요청/응답 타입 정의**
- `readBody<T>()`, `getQuery()`, `getRouterParam()` 의 타입 명시
5. **핸들러 구현**
- `defineEventHandler` 사용
- 입력 검증 추가
- 에러 처리는 `createError` 사용
6. **클라이언트 연결**
- 대응하는 composable에서 `useFetch('/api/xxx')` 로 연결 안내
7. **검증**
- TypeScript 오류 확인
- `nuxt dev` 에서 API 라우트 정상 동작 확인 안내
## 디렉토리 구조
```
server/
├── api/
│ ├── auth/
│ │ ├── login.post.ts
│ │ └── logout.post.ts
│ └── users/
│ ├── index.get.ts # GET /api/users
│ ├── index.post.ts # POST /api/users
│ └── [id].get.ts # GET /api/users/:id
├── middleware/
│ └── auth.ts # 모든 요청에 적용
└── utils/
└── api-client.ts # 공유 유틸리티
```
## GET 라우트 템플릿
```ts
// server/api/users/[id].get.ts
import type { UserProfile } from '~/types/user';
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id');
if (!id) {
throw createError({
statusCode: 400,
statusMessage: 'User ID is required',
});
}
const config = useRuntimeConfig();
const profile = await $fetch<UserProfile>(
`${config.apiBaseUrl}/users/${id}`,
{
headers: {
Authorization: getHeader(event, 'authorization') ?? '',
},
},
);
return profile;
});
```
## POST 라우트 템플릿
```ts
// server/api/users/index.post.ts
interface CreateUserBody {
name: string;
email: string;
}
export default defineEventHandler(async (event) => {
const body = await readBody<CreateUserBody>(event);
if (!body.name || !body.email) {
throw createError({
statusCode: 400,
statusMessage: 'Name and email are required',
});
}
const config = useRuntimeConfig();
const result = await $fetch(`${config.apiBaseUrl}/users`, {
method: 'POST',
body,
});
return result;
});
```
## 서버 미들웨어 템플릿
```ts
// server/middleware/auth.ts
export default defineEventHandler((event) => {
const protectedPaths = ['/api/protected', '/api/admin'];
const isProtected = protectedPaths.some((path) =>
event.path.startsWith(path),
);
if (!isProtected) {
return;
}
const token = getHeader(event, 'authorization');
if (!token) {
throw createError({
statusCode: 401,
statusMessage: 'Unauthorized',
});
}
});
```
## 서버 유틸리티 템플릿
```ts
// server/utils/api-client.ts
export function createApiClient() {
const config = useRuntimeConfig();
return {
get: <T>(path: string, headers?: Record<string, string>) =>
$fetch<T>(`${config.apiBaseUrl}${path}`, { headers }),
post: <T>(path: string, body: unknown, headers?: Record<string, string>) =>
$fetch<T>(`${config.apiBaseUrl}${path}`, {
method: 'POST',
body,
headers,
}),
};
}
```
## nuxt.config.ts 런타임 설정
```ts
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
// 서버 전용 (클라이언트에 노출 안 됨)
apiBaseUrl: process.env.API_BASE_URL || 'http://localhost:8080',
apiSecret: process.env.API_SECRET,
// 클라이언트에도 노출
public: {
appName: 'My App',
},
},
});
```
## 주의사항
- **비밀키는 `useRuntimeConfig()` 로만 접근**: 클라이언트 코드에 노출 금지. `runtimeConfig` (public이 아닌) 필드에 저장
- **서버 라우트는 Nitro(Node.js) 컨텍스트**: Vue 반응성(`ref`, `computed`), 브라우저 API(`window`, `document`) 사용 불가
- **`readBody<T>()` 입력 검증 필수**: 타입 단언만으로는 런타임 안전성 미보장. 필수 필드 체크 추가
- **`createError` 로 일관된 에러 응답**: `statusCode` + `statusMessage` 형태
- **외부 API 프록시 시 헤더 전달 주의**: 내부 전용 헤더가 외부로 유출되지 않도록 필요한 헤더만 선택 전달
- 파일 네이밍은 `kebab-case.ts` (`rules/coding-conventions.md` 참조)
- `any` 타입 사용 금지

View File

@@ -1,5 +1,5 @@
--- ---
name: edm-email-html name: markup-edm
description: | description: |
EDM(이메일 다이렉트 마케팅) HTML을 구현하는 전체 워크플로우 스킬. EDM(이메일 다이렉트 마케팅) HTML을 구현하는 전체 워크플로우 스킬.
Figma 디자인 → HTML table 마크업 → 아웃룩 호환 → 검수까지 단계별 가이드를 제공합니다. Figma 디자인 → HTML table 마크업 → 아웃룩 호환 → 검수까지 단계별 가이드를 제공합니다.

View File

@@ -0,0 +1,139 @@
---
name: nuxt-code-review
description: |
브랜치/PR 단위 코드 리뷰를 수행합니다. git diff 기반으로 변경된 코드 전체를
팀 컨벤션과 평가 기준에 따라 근거 기반(evidence-based)으로 리뷰합니다.
"코드 리뷰해줘", "PR 리뷰", "브랜치 리뷰", "변경사항 검토", "diff 리뷰" 등을
요청하면 트리거됩니다. (단일 .vue 파일 심층 리뷰는 vue-component-review 스킬을 사용하세요.)
---
# Nuxt 코드 리뷰 (브랜치/PR 단위)
이 스킬은 현재 브랜치의 전체 변경사항을 대상으로 근거 기반 코드 리뷰를 수행합니다.
모든 코멘트는 `references/evaluation-criteria.md`의 기준 ID를 인용하여 주관적 의견이 아닌
팀 컨벤션에 근거합니다.
## 핵심 원칙
1. **근거 인용 필수**: 모든 코멘트는 평가 기준 ID(예: `VUE-002`)를 반드시 인용한다. 기준 없는 의견은 코멘트로 작성하지 않는다.
2. **수정 제안 필수**: "이 코드가 잘못됐다"만으로는 부족하다. 구체적인 수정 방법(before/after)을 함께 제시한다.
3. **리플렉션으로 오탐 제거**: 최종 출력 전 모든 코멘트를 재검증하여 컨텍스트를 오해한 오탐을 제거한다.
## 작업 순서
### Phase 1: 컨텍스트 수집
1. 베이스 브랜치를 확인한다. 사용자가 명시하지 않으면 `main`으로 가정하고 알린다.
2. 변경 범위를 파악한다:
```bash
git diff <base>...HEAD --stat
git log <base>...HEAD --oneline
```
3. 프로젝트 전용 컨벤션 파일이 있으면 읽는다 (우선순위 높음):
- `.claude/project/conventions.md`
- `docs/adr.md` 또는 `docs/adr.yaml`
- `docs/code-convention.yaml`
4. `references/evaluation-criteria.md`를 로드하여 평가 기준을 준비한다.
5. 변경 의도를 1줄로 요약한다 (커밋 메시지, PR 설명, 파일 목록에서 추론).
6. **대규모 diff 처리**: 변경 파일이 30개 초과 또는 변경 라인이 2000줄 초과이면, 리뷰할 디렉토리나 파일 타입을 사용자에게 물어 범위를 축소한다.
### Phase 2: 구조화된 코드 리뷰
1. `git diff <base>...HEAD`로 전체 diff를 읽는다.
2. 아래 파일은 리뷰에서 제외한다:
- `.nuxt/`, `node_modules/`, `.output/` 내 파일
- 락 파일 (`package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`)
- 자동 생성 파일 (`*.generated.ts`, `*.d.ts` 중 빌드 산출물)
3. 파일별로 해당하는 평가 기준을 대조하며 검토한다.
4. 각 발견사항을 우선순위에 따라 분류한다:
- **[p1] 반드시 수정**: 버그, 런타임 에러, 보안 취약점, 팀 표준 위반 중 기능에 영향을 주는 것
- **[p2] 강력 권장**: 컨벤션 위반, 타입 누락, 유지보수 부담을 주는 패턴
- **[p3] 권장**: 코드를 더 좋게 만들지만 위반은 아닌 개선사항
- **[p4] 사소**: 스타일 지적, 네이밍 선호, 사소한 가독성
5. 크로스파일 이슈도 점검한다:
- 변경된 파일 간 패턴 불일치
- 타입 변경 후 사용처가 업데이트되지 않은 경우
- import/export 정합성
### Phase 3: 리뷰 리플렉션 (자기 검증)
모든 코멘트를 최종 출력 전에 재검증한다. 각 코멘트에 대해 스스로 묻는다:
- 인용한 평가 기준이 이 특정 코드 컨텍스트에 실제로 적용되는가?
- 작성자가 이 선택을 의도적으로 했을 수 있는가? (프로젝트 컨벤션, 기술적 제약)
- 제안한 수정이 실제로 올바르고, 사이드 이펙트는 없는가?
- 전체 컨텍스트를 보지 못해 생긴 오탐은 아닌가?
오탐으로 판단되면 해당 코멘트를 최종 출력에서 제거한다. 이 과정은 내부 검증이며 출력에 포함하지 않는다.
### Phase 4: 최종 출력
아래 출력 형식에 따라 최종 리뷰를 작성한다.
## 코멘트 형식
각 코멘트는 다음 구조를 따른다:
```
### [p1] src/components/UserCard.vue:45
- **기준**: VUE-002 (defineProps 제네릭 타입)
- **내용**: Props가 런타임 선언 방식으로 되어 있어 TypeScript 타입 안전성이 보장되지 않습니다.
- **제안**:
```ts
// Before
const props = defineProps({ name: String, age: Number });
// After
const props = defineProps<{ name: string; age: number }>();
```
- **사이드 이펙트**: 없음. 런타임 동작 동일.
- **제안 이유**: 제네릭 형태는 IDE 자동완성과 타입 추론에서 우수하며 팀 표준입니다.
```
## 출력 형식
```
## 코드 리뷰 결과
### 리뷰 범위
- 베이스: `main`
- 브랜치: `feature/user-profile`
- 변경 파일: N개 (파일 목록)
- 변경 의도: (1줄 요약)
---
### [p1] 반드시 수정 (N건)
(코멘트들)
### [p2] 강력 권장 (N건)
(코멘트들)
### [p3] 권장 (N건)
(코멘트들)
### [p4] 사소 (N건)
(코멘트들)
---
### 좋은 점
- (코드에서 잘 된 부분 관찰)
### 추가 리뷰 제안
- `UserCard.vue` — 250줄 초과. `vue-component-review` 스킬로 컴포넌트 상세 리뷰를 권장합니다.
```
## 주의사항
- **코드 수정 금지**: 사용자가 수정을 요청하지 않은 경우 리뷰만 수행한다. 수정이 필요하면 별도로 요청받는다.
- **프로젝트 지침 우선**: 프로젝트 전용 컨벤션과 팀 공통 지침이 충돌하면 프로젝트 지침을 따르되, 차이를 사용자에게 알린다.
- **생성 파일 제외**: `.nuxt/`, `node_modules/`, 자동 생성 파일, 락 파일은 리뷰하지 않는다.
- **단일 파일 심층 리뷰 연계**: 개별 `.vue` 파일의 체크리스트 기반 상세 리뷰가 필요하면 `vue-component-review` 스킬 사용을 제안한다.
- **커밋 컨벤션 확인**: 브랜치 커밋 메시지의 Conventional Commits 준수 여부는 `rules/commit-pr.md` 기준으로 확인하되, 커밋 메시지 생성은 `conventional-commit` 스킬의 역할이다.

View File

@@ -0,0 +1,95 @@
# Nuxt 코드 리뷰 평가 기준
이 문서는 `nuxt-code-review` 스킬에서 코멘트 작성 시 근거로 인용하는 평가 기준 목록입니다.
각 기준은 고유 ID를 가지며, 코멘트에 `기준: VUE-001 (제목)` 형태로 인용합니다.
---
## VUE — Vue 3 컴포넌트 패턴
| ID | 제목 | 규칙 | 기본 심각도 | 참조 |
|----|------|------|------------|------|
| VUE-001 | script setup 사용 | `<script setup lang="ts">`를 기본으로 사용한다. Options API는 신규 코드에서 사용하지 않는다. | p1 | rules/framework-rules.md |
| VUE-002 | defineProps 제네릭 타입 | Props는 `defineProps<T>()` 제네릭 형태로 타입을 명시한다. | p1 | rules/framework-rules.md |
| VUE-003 | defineEmits 제네릭 타입 | Emits는 `defineEmits<{ ... }>()` 제네릭 형태로 선언한다. | p1 | rules/framework-rules.md |
| VUE-004 | ref vs reactive 일관성 | 원시값·단일 객체는 `ref`, 복잡한 상태 트리는 `reactive`. 팀 내에서는 가능한 `ref` 우선. | p2 | rules/framework-rules.md |
| VUE-005 | computed 활용 | 계산된 값은 메서드 대신 `computed`로 정의한다. 템플릿에서 반복 호출되는 계산 로직은 반드시 computed로 추출한다. | p2 | rules/framework-rules.md |
| VUE-006 | 컴포넌트 단일 책임 | 컴포넌트는 단일 책임을 가진다. 200줄을 넘으면 분리를 검토한다. | p2 | rules/framework-rules.md |
| VUE-007 | 불리언 prop 네이밍 | 불리언 prop은 `is`, `has`, `can`, `should` 접두사를 사용한다. | p3 | rules/coding-conventions.md |
| VUE-008 | 이벤트 핸들러 네이밍 | 이벤트 핸들러 함수는 `handle` 또는 `on` 접두사를 사용한다. | p3 | rules/coding-conventions.md |
| VUE-009 | 불필요한 reactive 래핑 | 단순 객체에 `reactive`를 사용해 불필요한 프록시 오버헤드가 생기지 않도록 한다. | p3 | rules/framework-rules.md |
| VUE-010 | 복잡한 템플릿 표현식 | 템플릿 내 복잡한 인라인 로직은 computed나 메서드로 추출한다. | p2 | rules/coding-conventions.md |
| VUE-011 | 비즈니스 로직 composable 추출 | 재사용 가능한 비즈니스 로직은 `composables/` 디렉토리의 `useXxx` 함수로 추출한다. | p2 | rules/framework-rules.md |
| VUE-012 | 파일명 PascalCase | Vue 컴포넌트 파일명은 `PascalCase.vue`를 사용한다. | p2 | rules/coding-conventions.md |
---
## NUXT — Nuxt 3 프레임워크 패턴
| ID | 제목 | 규칙 | 기본 심각도 | 참조 |
|----|------|------|------------|------|
| NUXT-001 | 파일 기반 라우팅 | Nuxt의 파일 기반 라우팅을 사용한다. 수동 라우트 정의는 특수한 경우에만 허용한다. | p2 | rules/framework-rules.md |
| NUXT-002 | useFetch / useAsyncData 사용 | 서버 상태는 `useFetch` / `useAsyncData`를 사용한다. 컴포넌트에서 직접 `$fetch``fetch`를 호출하지 않는다. | p1 | rules/framework-rules.md |
| NUXT-003 | 동적 라우트 파라미터 형식 | 동적 라우트 파라미터는 `[param].vue` 형식을 사용한다. | p2 | rules/framework-rules.md |
| NUXT-004 | 미들웨어 위치 | 라우트 미들웨어는 `middleware/` 디렉토리에 배치하고, 필요한 경우에만 인라인 미들웨어를 사용한다. | p2 | rules/framework-rules.md |
| NUXT-005 | Nitro 서버 라우트 분리 | BFF 로직은 `server/api/` 또는 `server/routes/`에 배치하며, 클라이언트 컴포넌트에서 외부 API를 직접 호출하지 않는다. | p1 | rules/framework-rules.md |
| NUXT-006 | createError 사용 | 서버 라우트에서 에러 응답은 `createError`를 사용하여 일관된 에러 형식을 유지한다. | p2 | — |
| NUXT-007 | SEO 메타 관리 | 페이지 컴포넌트에는 `useHead` 또는 `useSeoMeta`로 메타 태그를 관리한다. | p3 | — |
| NUXT-008 | auto-import 활용 | Nuxt의 auto-import 대상(`ref`, `computed`, `useFetch` 등)은 명시적으로 import하지 않는다. | p4 | — |
---
## TS — TypeScript
| ID | 제목 | 규칙 | 기본 심각도 | 참조 |
|----|------|------|------------|------|
| TS-001 | any 사용 금지 | `any` 타입 사용을 금지한다. 불가피하면 `unknown`을 먼저 고려하고, 사용 시 주석으로 이유를 명시한다. | p1 | rules/coding-conventions.md |
| TS-002 | 함수 시그니처 타입 명시 | 함수의 매개변수와 반환 타입을 명시한다. | p1 | rules/coding-conventions.md |
| TS-003 | 공개 API 타입 export | 타 모듈에서 import되는 타입은 반드시 export한다. | p2 | rules/coding-conventions.md |
| TS-004 | 유니온 타입 관리 | 유니온 타입은 `as const` 또는 별도 타입 alias로 관리한다. 인라인 리터럴 유니온 남용을 피한다. | p2 | rules/coding-conventions.md |
| TS-005 | strict 모드 준수 | `tsconfig.json``strict: true`를 유지한다. 타입 오류를 `!` (non-null assertion)으로 억지 해결하지 않는다. | p1 | rules/framework-rules.md |
| TS-006 | 외부 API 응답 타입 정의 | 외부 API 응답은 반드시 타입을 정의하여 사용한다. 응답을 untyped로 사용하지 않는다. | p1 | rules/framework-rules.md |
---
## STYLE — Tailwind CSS / 스타일링
| ID | 제목 | 규칙 | 기본 심각도 | 참조 |
|----|------|------|------------|------|
| STYLE-001 | 유틸리티 클래스 우선 | Tailwind 유틸리티 클래스를 직접 사용한다. 공통 패턴은 컴포넌트로 추출한다. | p2 | rules/framework-rules.md |
| STYLE-002 | 임의값 클래스 제한 | `w-[123px]`과 같은 임의값 클래스는 디자인 시스템에 없는 값에만 제한적으로 사용한다. | p2 | rules/framework-rules.md |
| STYLE-003 | 조건부 클래스 clsx/cn | 조건부 클래스 조합은 `clsx` 또는 `cn` 유틸리티를 사용한다. 삼항 연산자 / 템플릿 리터럴 남용을 피한다. | p2 | rules/framework-rules.md |
| STYLE-004 | 클래스 순서 | 클래스 순서는 `prettier-plugin-tailwindcss` 프리셋을 따른다. | p4 | rules/framework-rules.md |
| STYLE-005 | @apply 제한 | `@apply`는 꼭 필요한 경우에만 사용한다. 가능한 유틸리티를 직접 나열한다. | p3 | rules/framework-rules.md |
---
## STATE — 상태 관리
| ID | 제목 | 규칙 | 기본 심각도 | 참조 |
|----|------|------|------------|------|
| STATE-001 | Pinia 사용 | 여러 컴포넌트가 공유하는 상태는 Pinia를 사용한다. 컴포넌트 간 props drilling이 3단계 이상이면 Pinia 도입을 검토한다. | p2 | rules/framework-rules.md |
| STATE-002 | Setup Store 패턴 | Pinia 스토어는 Composition API 스타일의 Setup Store로 작성한다. | p2 | rules/framework-rules.md |
| STATE-003 | 서버 상태와 클라이언트 상태 분리 | API 데이터(서버 상태)는 Pinia에 저장하지 않고 `useFetch`/`useAsyncData`로 관리한다. | p2 | rules/framework-rules.md |
| STATE-004 | SSR hydration 안전성 | 스토어 초기값이 SSR/CSR 환경에서 다르면 hydration 불일치가 발생할 수 있다. 서버에서만 유효한 값을 클라이언트 초기값으로 사용하지 않는다. | p1 | — |
---
## PERF — 성능
| ID | 제목 | 규칙 | 기본 심각도 | 참조 |
|----|------|------|------------|------|
| PERF-001 | 컴포넌트 lazy 로딩 | 초기 렌더에 필요하지 않은 무거운 컴포넌트는 `defineAsyncComponent` 또는 `<Lazy>` 접두사로 지연 로딩한다. | p3 | — |
| PERF-002 | 불필요한 반응성 | 변경될 필요가 없는 값을 `ref`/`reactive`로 감싸지 않는다. 정적 설정 객체, 상수 등은 반응형 불필요. | p3 | — |
| PERF-003 | computed vs method | 템플릿에서 반복 호출되는 값은 메서드 대신 `computed`를 사용하여 캐싱 이점을 활용한다. | p2 | rules/framework-rules.md |
| PERF-004 | 번들 사이즈 | 새 외부 라이브러리 추가 시 번들 영향과 대안을 PR 설명에 기록한다. 동일 기능 라이브러리 중복 도입을 금지한다. | p2 | rules/framework-rules.md |
---
## SEC — 보안
| ID | 제목 | 규칙 | 기본 심각도 | 참조 |
|----|------|------|------------|------|
| SEC-001 | 하드코딩 비밀정보 금지 | API 키, 토큰, 비밀번호 등을 소스코드에 하드코딩하지 않는다. 환경변수(`runtimeConfig`)를 사용한다. | p1 | rules/claude-workflow.md |
| SEC-002 | v-html XSS 방지 | `v-html`은 신뢰할 수 있는 서버 데이터에만 사용한다. 사용자 입력값을 `v-html`에 바인딩하지 않는다. | p1 | — |
| SEC-003 | 서버 라우트 입력 검증 | Nitro 서버 라우트에서 쿼리 파라미터, 바디 등 외부 입력값은 반드시 검증 후 사용한다. | p1 | — |

View File

@@ -0,0 +1,137 @@
---
name: nuxt-component-create
description: 새로운 Vue 3 컴포넌트를 팀 컨벤션에 맞게 생성할 때 사용합니다. "컴포넌트 만들어줘", "새 컴포넌트", "UI 컴포넌트 추가", "버튼 만들어줘", "카드 컴포넌트" 등을 요청하면 트리거됩니다. (기존 컴포넌트 리뷰는 vue-component-review 스킬을 사용하세요.)
---
# Vue 컴포넌트 신규 생성
이 skill은 팀 컨벤션에 맞는 **새로운** Vue 3 컴포넌트를 생성합니다.
기존 컴포넌트의 리뷰/점검은 `vue-component-review` skill을 사용합니다.
## 작업 순서
1. **요구사항 확인**
- 컴포넌트의 목적, 필요한 props, emits, 사용 위치를 파악
- 불명확한 부분은 사용자에게 질문
2. **기존 컴포넌트 탐색**
- `components/` 디렉토리에서 유사/재사용 가능한 컴포넌트 확인
- `.claude/project/conventions.md` 에서 프로젝트별 컴포넌트 규칙 확인
3. **파일 위치 결정**
- 프로젝트의 기존 디렉토리 구조 패턴을 따름
- 일반적 구조: `components/base/` (Atoms), `components/common/` (Molecules), `components/domain/` (Organisms)
4. **인터페이스 우선 설계**
- `defineProps<T>()` 제네릭 형태로 props 타입 정의
- `defineEmits<{}>()` 제네릭 형태로 events 선언
- 필요 시 `defineSlots` 로 슬롯 타입 정의
5. **구현**
- `<script setup lang="ts">` 사용
- 로직이 30줄을 넘으면 composable 추출 검토
- 템플릿의 가독성 유지
6. **스타일링**
- Tailwind 유틸리티 클래스 우선
- 조건부 클래스는 `clsx` 또는 `cn` 유틸리티 사용
- `@apply` 는 꼭 필요한 경우에만
7. **검증**
- TypeScript 오류 확인
- 파일 길이 200줄 이내 확인
## 컴포넌트 템플릿
```vue
<script setup lang="ts">
interface Props {
/** 버튼 라벨 */
label: string;
isDisabled?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
isDisabled: false,
});
const emit = defineEmits<{
click: [event: MouseEvent];
}>();
function handleClick(event: MouseEvent) {
if (!props.isDisabled) {
emit('click', event);
}
}
</script>
<template>
<button
:disabled="isDisabled"
class="rounded-lg bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"
@click="handleClick"
>
{{ label }}
</button>
</template>
```
## 슬롯이 있는 컴포넌트 템플릿
```vue
<script setup lang="ts">
interface Props {
title: string;
isCollapsible?: boolean;
}
withDefaults(defineProps<Props>(), {
isCollapsible: false,
});
const isOpen = ref(true);
function handleToggle() {
isOpen.value = !isOpen.value;
}
</script>
<template>
<div class="rounded-lg border border-gray-200 p-4">
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold">{{ title }}</h3>
<button v-if="isCollapsible" @click="handleToggle">
{{ isOpen ? '접기' : '펼치기' }}
</button>
</div>
<div v-show="isOpen" class="mt-4">
<slot />
</div>
</div>
</template>
```
## 분리 기준 가이드
| 상황 | 조치 |
| --- | --- |
| 파일 200줄 초과 | 하위 컴포넌트로 분리 |
| script 로직 30줄 초과 | composable로 추출 |
| Props 5개 초과 | 객체 prop으로 그룹핑 검토 |
| 동일 패턴 3회 이상 반복 | 공통 컴포넌트로 추출 |
## 네이밍 규칙
- 파일명: `PascalCase.vue` (예: `UserCard.vue`)
- 이벤트 핸들러: `handle*` 또는 `on*` 접두사
- 불리언 props: `is*`, `has*`, `can*`, `should*` 접두사
- 상수: `UPPER_SNAKE_CASE`
## 주의사항
- Options API 사용 금지. 반드시 `<script setup lang="ts">`
- `any` 타입 사용 금지. 불가피 시 주석으로 사유 기재, `unknown` 우선 고려
- 직접 `fetch`/`$fetch` 호출 금지. composable 또는 API wrapper 사용
- 새 컴포넌트 생성 전 기존 컴포넌트 재사용 가능 여부를 반드시 먼저 확인
- 사용자가 리팩토링을 요청하지 않은 경우 기존 코드를 수정하지 않음

View File

@@ -0,0 +1,186 @@
---
name: nuxt-composable
description: Nuxt 3 composable(useXxx)과 데이터 페칭(useFetch/useAsyncData) 패턴을 작성할 때 사용합니다. "composable 만들어줘", "useFetch 패턴", "데이터 페칭", "useAsyncData", "커스텀 훅", "로직 추출" 등을 요청하면 트리거됩니다.
---
# Nuxt Composable · 데이터 페칭
이 skill은 Nuxt 3의 composable 함수와 데이터 페칭 패턴을 팀 컨벤션에 맞게 생성합니다.
데이터 페칭 composable(서버 상태)과 로직 composable(UI 상태/행동)을 구분하여 작성합니다.
## 작업 순서
1. **관심사 식별**
- 데이터 페칭 composable인가? (API 호출, 서버 상태)
- 로직 composable인가? (UI 상태, 재사용 행동)
2. **기존 composable 탐색**
- `composables/` 디렉토리에서 기존 패턴 확인
- 중복 생성 방지
3. **네이밍 결정**
- `use` 접두사 + PascalCase 도메인명
- 파일: `composables/useXxx.ts` (Nuxt auto-import 대상)
- 예: `useAuth`, `useUserProfile`, `useProductList`, `useToggle`
4. **데이터 페칭 전략 선택**
- 아래 판단 기준표를 참고하여 적합한 방식 결정
5. **구현**
- 응답 타입을 반드시 명시: `useFetch<ResponseType>()`
- 상태와 메서드를 하나의 객체로 반환
- 부수효과 최소화
6. **검증**
- TypeScript 오류 확인
- SSR 환경에서의 동작 고려
## 데이터 페칭 판단 기준표
| 시나리오 | 추천 방식 | 이유 |
| --- | --- | --- |
| 단순 REST GET | `useFetch` | 자동 key 중복 방지, 간결함 |
| 커스텀 key·transform 필요 | `useAsyncData` | 캐싱/변환 세밀 제어 |
| 이벤트 핸들러 내 POST/PUT/DELETE | `$fetch` | SSR 불필요, fire-and-forget |
| 의존 쿼리 (체이닝) | `useAsyncData` + `watch` | 실행 순서 제어 |
| 서버 사이드 전용 로직 | `server/api/` + `$fetch` | Nitro 컨텍스트 |
## 데이터 페칭 Composable 템플릿
```ts
// composables/useUserProfile.ts
import type { UserProfile } from '~/types/user';
export function useUserProfile(userId: Ref<string>) {
const { data, error, status, refresh } = useFetch<UserProfile>(
() => `/api/users/${userId.value}`,
{
key: `user-profile-${userId.value}`,
watch: [userId],
},
);
return {
profile: data,
error,
isLoading: computed(() => status.value === 'pending'),
refresh,
};
}
```
## useAsyncData 패턴
```ts
// composables/useProductList.ts
import type { Product } from '~/types/product';
interface UseProductListOptions {
category?: Ref<string>;
page?: Ref<number>;
}
export function useProductList(options: UseProductListOptions = {}) {
const { category, page } = options;
const { data, error, status, refresh } = useAsyncData(
'product-list',
() => $fetch<Product[]>('/api/products', {
query: {
category: category?.value,
page: page?.value,
},
}),
{
watch: [category, page].filter(Boolean),
},
);
return {
products: data,
error,
isLoading: computed(() => status.value === 'pending'),
refresh,
};
}
```
## 로직 Composable 템플릿
```ts
// composables/useToggle.ts
export function useToggle(initialValue = false) {
const isOpen = ref(initialValue);
function toggle() {
isOpen.value = !isOpen.value;
}
function open() {
isOpen.value = true;
}
function close() {
isOpen.value = false;
}
return { isOpen, toggle, open, close };
}
```
## 리스트 + 페이지네이션 패턴
```ts
// composables/usePaginatedList.ts
interface UsePaginatedListOptions<T> {
url: string;
pageSize?: number;
}
export function usePaginatedList<T>(options: UsePaginatedListOptions<T>) {
const { url, pageSize = 20 } = options;
const currentPage = ref(1);
const { data, error, status, refresh } = useFetch<{
items: T[];
total: number;
}>(
() => url,
{
query: computed(() => ({
page: currentPage.value,
size: pageSize,
})),
watch: [currentPage],
},
);
const totalPages = computed(() =>
Math.ceil((data.value?.total ?? 0) / pageSize),
);
function goToPage(page: number) {
currentPage.value = page;
}
return {
items: computed(() => data.value?.items ?? []),
currentPage: readonly(currentPage),
totalPages,
error,
isLoading: computed(() => status.value === 'pending'),
goToPage,
refresh,
};
}
```
## 주의사항
- **`$fetch``<script setup>` 에서 직접 사용 금지**: SSR 시 서버/클라이언트 양쪽에서 실행되어 이중 요청 발생. 반드시 `useFetch` 또는 `useAsyncData`로 감싸야 함
- composable 파일은 `composables/` 디렉토리에 `use` 접두사로 작성 (Nuxt auto-import)
- 반환값은 개별 ref가 아닌 **객체로 묶어서** 반환. 소비자가 구조 분해 결정
- 부수효과(side effect)를 최소화하고, 상태와 메서드를 함께 반환
- 응답 타입 반드시 명시: `useFetch<T>()`, `useAsyncData<T>()`
- `any` 타입 사용 금지 (`rules/coding-conventions.md` 참조)
- 서버 상태는 Pinia에 넣지 않음. `useFetch`/`useAsyncData`가 담당

View File

@@ -0,0 +1,109 @@
---
name: nuxt-page-layout
description: Nuxt 3 페이지, 레이아웃, 미들웨어를 생성할 때 사용합니다. "페이지 만들어줘", "레이아웃 추가", "미들웨어 작성", "라우트 추가", "새 페이지", "page", "layout", "middleware" 등을 요청하면 트리거됩니다.
---
# Nuxt 페이지·레이아웃·미들웨어 생성
이 skill은 Nuxt 3의 파일 기반 라우팅 체계에 맞춰 페이지, 레이아웃, 미들웨어를 생성합니다.
프로젝트 컨벤션(`.claude/project/conventions.md`)이 있으면 해당 규칙을 **우선**합니다.
## 작업 순서
1. **기존 구조 탐색**
- `pages/` 디렉토리 트리를 확인하여 라우팅 구조 파악
- `layouts/` 에서 기존 레이아웃 확인
- `middleware/` 에서 기존 미들웨어 확인
2. **라우트 구조 결정**
- 사용자와 파일 기반 라우팅 경로를 확인
- 동적 파라미터: `[id].vue`, catch-all: `[...slug].vue`, 중첩 라우트 처리
- 수동 라우트 정의는 특수한 경우에만 허용 (`rules/framework-rules.md` 참조)
3. **레이아웃 선택 또는 생성**
- 기존 레이아웃이 적합하면 재사용
- 새 레이아웃이 필요한 경우에만 생성
- `definePageMeta({ layout: 'xxx' })` 로 페이지에 연결
4. **페이지 파일 생성**
- `<script setup lang="ts">` 템플릿 사용
- `definePageMeta` 로 layout, middleware, title 설정
- `useSeoMeta` 로 SEO 메타태그 설정
5. **미들웨어 추가 (필요 시)**
- named middleware: `middleware/auth.ts``definePageMeta({ middleware: ['auth'] })`
- inline middleware: 페이지 내 `definePageMeta({ middleware: [...] })`
6. **검증**
- TypeScript 오류 없는지 확인
- `nuxt dev` 에서 라우트가 정상 인식되는지 안내
## 페이지 템플릿
```vue
<script setup lang="ts">
definePageMeta({
layout: 'default',
middleware: ['auth'],
});
useSeoMeta({
title: '페이지 제목',
ogTitle: '페이지 제목',
description: '페이지 설명',
});
</script>
<template>
<div>
<!-- content -->
</div>
</template>
```
## 레이아웃 템플릿
```vue
<script setup lang="ts">
</script>
<template>
<div class="min-h-screen">
<AppHeader />
<main>
<slot />
</main>
<AppFooter />
</div>
</template>
```
## Named 미들웨어 템플릿
```ts
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
const { isLoggedIn } = useAuth();
if (!isLoggedIn.value) {
return navigateTo('/login');
}
});
```
## 동적 라우트 패턴
| 파일명 | 라우트 | 예시 URL |
| --- | --- | --- |
| `pages/users/[id].vue` | `/users/:id` | `/users/123` |
| `pages/posts/[...slug].vue` | `/posts/:slug(.*)` | `/posts/2026/04/hello` |
| `pages/[[optional]].vue` | `/:optional?` | `/` 또는 `/about` |
## 주의사항
- `definePageMeta` 에는 **정적/직렬화 가능한 값**만 허용. 런타임 표현식 사용 불가
- 동적 라우트 파라미터는 `[param].vue` 형식 사용 (`_param.vue` 는 Nuxt 2 문법)
- `pages/` 외부에 페이지 파일을 생성하지 않음
- 한국어 SEO: `useSeoMeta``lang="ko"` 관련 설정 고려
- 컴포넌트 네이밍은 `PascalCase` (`rules/coding-conventions.md` 참조)
- 사용자가 명시적으로 요청하지 않는 한 기존 파일을 수정하지 않음