Communitygithub.com

vue-patterns

Vue.js 3 Composition API 패턴, 컴포넌트 아키텍처, 반응형 모범 사례, Pinia 상태 관리, Vue Router 내비게이션 및 Nuxt SSR 패턴. Vue, Nuxt, Vite 또는 Pinia 프로젝트에서 활성화됩니다. Vue 3, Nuxt 또는 Pinia 코드를 작성하거나 검토할 때 사용하세요 — Composition API, 반응형 또는 라우터 내비게이션.

vue-patterns란 무엇인가요?

이 스킬은 Vue.js 3 개발자를 위한 포괄적인 가이드로, Composition API (`<script setup>`)를 기반으로 컴포넌트 설계, 반응형 시스템, 상태 관리(Pinia), 라우팅(Vue Router), 테스트, SSR 패턴을 다룹니다. Claude Code, Cursor, Codex와 같은 AI 에이전트에서 Vue, Nuxt, Vite 또는 Pinia 프로젝트를 분석하거나 코드를 생성할 때 자동으로 활성화됩니다. 사용자는 이 스킬을 통해 Vue 3.5+의 최신 API(`useTemplateRef()`, `onWatcherCleanup()`, `defer` 텔레포트 등)를 포함한 모범 사례와 함께, 파일 구조, 컴포저블 작성법, Pinia Setup Store, 반응형 props 구조 분해, 지연 로딩, Nuxt 전용 패턴(`useAsyncData`, 서버 라우트) 등을 학습할 수 있습니다. 또한, `v-if`와 `v-for`의 잘못된 사용, props 직접 변이, 믹스인 사용 등의 안티 패턴을 식별하고 올바른 해결 방법을 제시합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/vue-patterns

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Vue.js 패턴 및 모범 사례

Composition API (<script setup>)를 사용한 Vue.js 3 개발을 위한 포괄적인 가이드입니다. 컴포넌트 설계, 반응형, 상태 관리, 라우팅, 테스트 및 SSR 패턴을 다룹니다. 바닐라 Vue와 다른 경우 Nuxt 관련 지침도 포함되어 있습니다.

활성화 시점

다음과 같은 경우 이 스킬을 활성화하세요:

  • 프로젝트가 Vue.js (모든 버전), Nuxt, Vite + Vue 또는 Pinia를 사용하는 경우.
  • 사용자가 Vue 컴포넌트 아키텍처, 컴포저블, 반응형 또는 상태 관리에 대해 질문하는 경우.
  • Vue 단일 파일 컴포넌트 (.vue 파일)를 검토하는 경우.
  • Vue Router, Pinia 스토어 또는 Vite/Vitest 구성을 설정하는 경우.
  • Vue 특정 성능, 보안 또는 SSR 패턴에 대해 논의하는 경우.

1. 프로젝트 구조

권장 레이아웃 (기능 우선)

src/
├── api/              # API 클라이언트 및 엔드포인트 정의
├── assets/           # 정적 자산 (이미지, 폰트, 아이콘)
├── components/       # 공유/재사용 가능한 컴포넌트
│   ├── base/         # 기본 UI 프리미티브 (Button, Input, Modal)
│   └── features/     # 기능별 공유 컴포넌트
├── composables/      # 재사용 가능한 Composition API 로직
├── layouts/          # 페이지 레이아웃 (선택 사항)
├── pages/            # 라우트 수준 페이지 컴포넌트
├── router/           # Vue Router 구성
├── stores/           # Pinia 스토어
├── types/            # TypeScript 타입 정의
├── utils/            # 순수 유틸리티 함수
└── App.vue           # 루트 컴포넌트

파일 명명 규칙

규칙사용 시점
PascalCase.vue모든 컴포넌트 (vue/multi-word-component-names에 의해 강제됨)
useCamelCase.ts컴포저블
camelCase.ts유틸리티, API 클라이언트, 타입
kebab-case 디렉토리라우트 세그먼트, 기능 폴더

2. 컴포넌트 아키텍처

단일 파일 컴포넌트 순서

<script setup lang="ts">
// 1. 임포트 (vue → 생태계 → 절대 경로 → 상대 경로)
// 2. Props & Emits & Slots
// 3. 컴포저블
// 4. 로컬 상태 (ref/reactive)
// 5. 계산된 속성
// 6. 메서드
// 7. 감시자
// 8. 라이프사이클 훅
</script>

<template>
  <!-- 템플릿 콘텐츠 -->
</template>

<style scoped>
  /* 스코프 스타일 */
</style>

표현형 vs 컨테이너

  • 컨테이너 컴포넌트: 데이터 페칭, 상태 및 사이드 이펙트를 소유합니다. 표현형 컴포넌트를 렌더링합니다.
  • 표현형 컴포넌트: props를 받고 이벤트를 발생시킵니다. API 호출이나 스토어 접근이 없습니다. 순수 렌더링입니다.

Props 모범 사례

// 기본값이 있는 타입 기반 props
interface Props {
  label: string;
  variant?: "primary" | "secondary";
  disabled?: boolean;
  items: Item[];
}

const props = withDefaults(defineProps<Props>(), {
  variant: "primary",
  disabled: false,
});
  • 항상 type과 적절한 required/default를 제공하세요.
  • Boolean props: isXxx, hasXxx, canXxx.
  • props를 절대 변이하지 마세요. 대신 이벤트를 발생시키세요.
  • v-model 바인딩의 경우 defineModel() (Vue 3.4+) 또는 modelValue + update:modelValue를 사용하세요.

이벤트

const emit = defineEmits<{
  submit: [];
  "update:modelValue": [value: string];
  select: [id: string, index: number];
}>();
  • 템플릿에서는 kebab-case를 사용하세요 (@update:model-value).
  • 스크립트에서는 camelCase를 사용하세요 (emit("update:modelValue", val)).

3. 컴포저블 (재사용 가능한 로직)

구조

// composables/useDebounce.ts
export function useDebounce<T>(value: MaybeRef<T>, delay: number): Ref<T> {
  const debounced = ref(toValue(value)) as Ref<T>;

  let timer: ReturnType<typeof setTimeout>;
  watch(
    () => toValue(value),
    (newVal) => {
      clearTimeout(timer);
      timer = setTimeout(() => { debounced.value = newVal; }, delay);
    }
  );

  onUnmounted(() => clearTimeout(timer));
  return readonly(debounced);
}

규칙

  • use 접두사로 시작해야 합니다.
  • 반응형 값 (ref, computed, reactive)을 반환하고, 일반 프리미티브는 절대 반환하지 마세요.
  • MaybeRef / toRef() / toValue()를 통해 반응형 입력을 받으세요.
  • onUnmounted 또는 감시자 onCleanup에서 사이드 이펙트를 정리하세요.
  • 모듈 수준의 사이드 이펙트는 없어야 합니다.

vs 믹스인

컴포저블은 Vue 2 믹스인을 완전히 대체합니다:

  • 믹스인: 불투명한 데이터 흐름, 충돌하는 진리 소스, 이름 충돌.
  • 컴포저블: 명시적인 임포트, 명확한 반환 값, 구성 가능하고 트리 셰이크 가능합니다.

4. 상태 관리

언제 무엇을 사용할까

패턴사용 사례
ref() / reactive()로컬 컴포넌트 상태
Props + Emits부모-자식 통신
Provide / Inject테마, 설정, 플러그인 API
Pinia 스토어전역, 공유, 복잡한 상태
서버 상태 컴포저블캐싱이 있는 API 데이터 (fetch/TanStack Query 래핑)

Pinia Setup Store (권장)

// stores/useCartStore.ts
export const useCartStore = defineStore("cart", () => {
  const items = ref<CartItem[]>([]);
  const isLoading = ref(false);

  const totalPrice = computed(() =>
    items.value.reduce((sum, i) => sum + i.price * i.quantity, 0)
  );
  const itemCount = computed(() =>
    items.value.reduce((sum, i) => sum + i.quantity, 0)
  );

  async function addItem(productId: string) {
    isLoading.value = true;
    try {
      const item = await fetchProduct(productId);
      const existing = items.value.find(i => i.id === item.id);
      if (existing) existing.quantity++;
      else items.value.push({ ...item, quantity: 1 });
    } finally {
      isLoading.value = false;
    }
  }

  return { items, isLoading, totalPrice, itemCount, addItem };
});
  • Setup Store 구문 (Options Store 아님)을 사용하세요.
  • 비즈니스 수준의 변이에는 액션을, 그룹화된 업데이트에는 $patch()를 선호하세요.
  • 모든 비동기 액션: 로딩 + 성공 + 오류를 처리하세요.

5. Vue Router

라우트 정의

const routes = [
  {
    path: "/users/:id",
    name: "user-detail",
    component: () => import("@/pages/UserDetail.vue"), // 지연 로딩
    props: true, // params를 props로 전달
    meta: { requiresAuth: true },
  },
];

내비게이션 가드

router.beforeEach((to, from) => {
  const { isLoggedIn } = useAuthStore();
  if (to.meta.requiresAuth && !isLoggedIn) {
    return { name: "login", query: { redirect: to.fullPath } };
  }
});

반응형 라우트 파라미터

컴포넌트가 마운트된 상태로 유지되지만 라우트 파라미터가 변경될 때:

const route = useRoute();
const id = computed(() => route.params.id as string);
watch(id, (newId) => fetchItem(newId));

6. 템플릿 패턴

템플릿 구문

<!-- v-if/v-else-if/v-else -->
<div v-if="isLoading">로딩 중...</div>
<div v-else-if="error">오류: {{ error }}</div>
<div v-else>{{ content }}</div>

<!-- v-show: 빈번한 토글용 -->
<div v-show="isOpen">토글된 콘텐츠</div>

<!-- v-for: 안정적인 키 사용 -->
<div v-for="item in items" :key="item.id">{{ item.name }}</div>

<!-- 계산된 필터 목록 (같은 요소에 v-if + v-for 사용 금지) -->
<div v-for="item in activeItems" :key="item.id">{{ item.name }}</div>

<!-- 이벤트 처리 -->
<form @submit.prevent="handleSubmit">
  <button type="submit">저장</button>
</form>

<!-- v-model -->
<input v-model="name" />
<CustomInput v-model="value" v-model:title="title" />

7. 성능

기술사용 시점
v-memo거의 변경되지 않는 목록 항목
v-once한 번 렌더링되고 영원히 정적인 콘텐츠
shallowRef()통째로 교체되는 대규모 데이터 구조
shallowReactive()최상위 속성만 반응형인 경우
v-if 대신 v-show빈번한 가시성 토글
<KeepAlive :max="10">토글된 뷰 캐싱
지연 라우트중요하지 않은 라우트에 () => import(...)
Suspense폴백이 있는 비동기 컴포넌트 로딩

8. 테스팅

스택

  • Vitest: 단위 및 컴포넌트 테스트용
  • Vue Test Utils: 마운팅 및 상호작용용
  • @pinia/testing: 스토어 모킹용
  • Playwright: E2E 테스트용

컴포넌트 테스트 패턴

import { mount } from "@vue/test-utils";
import { createPinia, setActivePinia } from "pinia";
import UserCard from "./UserCard.vue";

beforeEach(() => { setActivePinia(createPinia()); });

it("렌더링 및 이벤트 발생", async () => {
  const wrapper = mount(UserCard, {
    props: { user: { id: "1", name: "Alice" } },
  });
  expect(wrapper.text()).toContain("Alice");
  await wrapper.find("button").trigger("click");
  expect(wrapper.emitted("select")![0]).toEqual(["1"]);
});

9. Nuxt 특정 패턴

자동 임포트

Nuxt는 ref, computed, watch, useFetch, useAsyncData 등을 자동 임포트합니다. 임포트 없이 직접 사용하세요. Nuxt가 아닌 프로젝트에서는 항상 명시적으로 임포트하세요.

useAsyncData / useFetch

const { data: user, pending, error, refresh } = await useAsyncData(
  "user", // 캐싱을 위한 고유 키
  () => $fetch(`/api/users/${id}`),
);

const { data: posts } = await useFetch("/api/posts", {
  query: { page: 1 },
  key: "posts-page-1", // 요청 중복 제거
});

서버 라우트

// server/api/users/[id].ts
export default defineEventHandler(async (event) => {
  const { id } = await getValidatedRouterParams(event, z.object({
    id: z.string().uuid(),
  }).parse);
  // ... fetch and return
});

런타임 설정

// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    // 서버 전용
    apiSecret: "",
    // 공개 (클라이언트에 노출됨)
    public: {
      apiBase: "https://api.example.com",
    },
  },
});

10. Vue 3.5+ 새로운 API

반응형 Props 구조 분해

Vue 3.5는 반응형 props 구조 분해를 안정화했습니다 — defineProps()에서 구조 분해된 변수는 자동으로 반응형입니다:

// Vue 3.5+: 구조 분해된 props는 반응형입니다 (toRefs 필요 없음)
const { count = 0, msg = "hello" } = defineProps<{
  count?: number;
  msg?: string;
}>();

// 제한 사항: 구조 분해된 prop을 직접 감시할 수 없습니다
watch(() => count, (newVal) => { ... }); // 통과: getter 필요

useTemplateRef()

이름이 일치하는 일반 ref를 useTemplateRef()로 대체하여 템플릿 참조를 얻으세요:

import { useTemplateRef } from "vue";
const inputEl = useTemplateRef<HTMLInputElement>("input");
// "input"은 템플릿의 ref="input" 속성과 일치하며, 변수 이름이 아닙니다

동적 ref ID 지원: useTemplateRef(dynamicRefId).

onWatcherCleanup()

전역에서 임포트 가능한 감시자 정리 API (Vue 3.5+). 감시자 콜백 내부에서 동기적으로 호출되어야 합니다:

import { watch, onWatcherCleanup } from "vue";

watch(userId, async (newId) => {
  const controller = new AbortController();
  onWatcherCleanup(() => controller.abort());
  // ... 신호로 fetch
});

useId()

폼 요소 및 접근성을 위한 SSR 안정적인 고유 ID 생성:

import { useId } from "vue";
const id = useId();

defer 텔레포트

<Teleport defer>를 사용하면 동일한 사이클에서 렌더링된 대상으로 텔레포트할 수 있습니다:

<Teleport defer to="#container">콘텐츠</Teleport>
<div id="container"></div>

지연 수화 (SSR)

defineAsyncComponent()는 이제 hydrate 전략을 지원합니다:

import { defineAsyncComponent, hydrateOnVisible } from "vue";
const AsyncComp = defineAsyncComponent({
  loader: () => import("./Comp.vue"),
  hydrate: hydrateOnVisible(),
});

안티 패턴

안티 패턴잘못된 이유해결 방법
defineProps() 구조 분해 (Vue < 3.5)스냅샷을 캡처하여 반응성을 잃음props.xxx로 접근하거나 toRefs() 사용
구조 분해된 prop에 watch() 사용 (Vue 3.5+)컴파일 타임 오류 — 구조 분해된 props는 직접 감시할 수 없음getter 래퍼 사용: watch(() => count, ...)
같은 요소에 v-if + v-for 사용모호한 실행 순서계산된 필터 배열 사용
v-for 키 = 인덱스재정렬 시 상태 손상안정적인 데이터베이스 ID 사용
props 변이단방향 데이터 흐름 위반이벤트 발생 또는 v-model 사용
사용자 콘텐츠에 v-html 사용XSS 취약점DOMPurify로 살균
Vue 3에서 믹스인 사용불투명하고 충돌하기 쉬움컴포저블로 대체
컴포저블의 모듈 수준 사이드 이펙트인스턴스 간에 공유됨onMounted + onUnmounted로 범위 지정
교체 가능한 상태에 reactive() 사용교체 시 반응성 손상대신 ref() 사용
정리 없는 감시자메모리 누수, 경쟁 조건onCleanup 또는 onWatcherCleanup() 사용 (Vue 3.5+)
새 Vue 3 코드에서 Options API 사용생태계가 Composition API로 이동 중<script setup> 사용
템플릿 참조에 일반 ref 사용동적 ref 지원 없음, 이름 일치가 취약함useTemplateRef() 사용 (Vue 3.5+)

관련 스킬

  • accessibility — ARIA, 시맨틱 HTML, 포커스 관리
  • frontend-patterns — 프레임워크를 넘나드는 프론트엔드 아키텍처
  • typescript — Vue 프로젝트에 적용된 TypeScript 모범 사례
  • coding-standards — 일반 코드 품질 표준

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

관련 스킬