Compare commits
2 Commits
f6cb59ac8b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| eebbbea6af | |||
| b75a733d4a |
1124
Nuxt FE AI 표준화 로드맵 v1.1.html
Normal file
1124
Nuxt FE AI 표준화 로드맵 v1.1.html
Normal file
File diff suppressed because it is too large
Load Diff
1
Nuxt FE AI 표준화 로드맵 v1.1_files/pretendard.min.css
vendored
Normal file
1
Nuxt FE AI 표준화 로드맵 v1.1_files/pretendard.min.css
vendored
Normal 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
85
docs/skills-by-phase.md
Normal 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
613
docs/standard.md
Normal 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 | 핵심 활동 | 산출물 |
|
||||
| --- | --- | --- | --- |
|
||||
| **W1–W2** | Phase 1 | 요구사항 분석 Skill 개발 및 테스트 | `requirement-analyzer` |
|
||||
| **W2–W3** | Phase 2 / 2-0 / 2-1 / 2-2 / 2-3 | 마크업 컨벤션 + PSD→Figma·피그마 마크업·프로모션·EDM Skill 개발 | `markup`, `markup-promotion`, `markup-edm` |
|
||||
| **W3–W5** | 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` |
|
||||
| **W5–W7** | Phase 4 / 4-1 / 4-2 | API/Pinia 패턴 표준화 + Pinia 스토어 + Nitro 서버 라우트 Skill 개발 | `nuxt-api-state-manager`, `pinia-store`, `nuxt-api-route` |
|
||||
| **W7–W8** | Phase 5 검증 (5-1) | 요구사항 검증 Skill 개발 | `requirement-optimizer` NEW |
|
||||
| **W8–W9** | Phase 5-2 | 성능 최적화 Skill 개발 및 파일럿 | `perf-optimizer` CHANGED |
|
||||
| **W9–W10** | Phase 6 NEW | SEO·GEO·AEO 감사 Skill 개발 | `nuxt-seo-geo` |
|
||||
| **W11–W12** | 통합 | 전체 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
|
||||
147
skills/api-pinia-store/SKILL.md
Normal file
147
skills/api-pinia-store/SKILL.md
Normal 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` 참조)
|
||||
194
skills/api-server-route/SKILL.md
Normal file
194
skills/api-server-route/SKILL.md
Normal 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` 타입 사용 금지
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: edm-email-html
|
||||
name: markup-edm
|
||||
description: |
|
||||
EDM(이메일 다이렉트 마케팅) HTML을 구현하는 전체 워크플로우 스킬.
|
||||
Figma 디자인 → HTML table 마크업 → 아웃룩 호환 → 검수까지 단계별 가이드를 제공합니다.
|
||||
139
skills/nuxt-code-review/SKILL.md
Normal file
139
skills/nuxt-code-review/SKILL.md
Normal 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` 스킬의 역할이다.
|
||||
95
skills/nuxt-code-review/references/evaluation-criteria.md
Normal file
95
skills/nuxt-code-review/references/evaluation-criteria.md
Normal 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 | — |
|
||||
137
skills/nuxt-component-create/SKILL.md
Normal file
137
skills/nuxt-component-create/SKILL.md
Normal 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 사용
|
||||
- 새 컴포넌트 생성 전 기존 컴포넌트 재사용 가능 여부를 반드시 먼저 확인
|
||||
- 사용자가 리팩토링을 요청하지 않은 경우 기존 코드를 수정하지 않음
|
||||
186
skills/nuxt-composable/SKILL.md
Normal file
186
skills/nuxt-composable/SKILL.md
Normal 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`가 담당
|
||||
109
skills/nuxt-page-layout/SKILL.md
Normal file
109
skills/nuxt-page-layout/SKILL.md
Normal 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` 참조)
|
||||
- 사용자가 명시적으로 요청하지 않는 한 기존 파일을 수정하지 않음
|
||||
Reference in New Issue
Block a user