Communitygithub.com

Vue 패턴 및 모범 사례

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

Vue 패턴 및 모범 사례란 무엇인가요?

이 스킬은 Vue.js 3 개발을 위한 포괄적인 가이드입니다. Composition API (`<script setup>`)를 중심으로 컴포넌트 설계, 반응성, 상태 관리, 라우팅, 테스트 및 SSR 패턴을 다룹니다. Nuxt 관련 지침도 포함되어 있습니다. 이 스킬은 Vue.js (모든 버전), Nuxt, Vite + Vue, 또는 Pinia를 사용하는 프로젝트에서 활성화됩니다. 사용자가 Vue 컴포넌트 아키텍처, 컴포저블, 반응성, 상태 관리에 대해 질문하거나 Vue 싱글 파일 컴포넌트를 검토할 때, Vue Router, Pinia 스토어, Vite/Vitest 구성을 설정할 때, Vue 관련 성능, 보안 또는 SSR 패턴을 논의할 때 이 스킬이 활성화됩니다. 이 스킬은 기능 우선 프로젝트 구조, 프레젠테이셔널 vs 컨테이너 컴포넌트, props 및 이벤트 모범 사례, 재사용 가능한 컴포저블, Pinia 셋업 스토어, Vue Router 경로 정의 및 네비게이션 가드, 템플릿 패턴, 성능 최적화 기법, Vitest 및 Vue Test Utils를 사용한 테스트, Nuxt 특정 패턴 (자동 임포트, useAsyncData, 서버 라우트, 런타임 설정), Vue 3.5+ 새로운 API (반응형 props 구조 분해, useTemplateRef, onWatcherCleanup, useId, 지연 텔레포트, 지연 하이드레이션) 및 일반적인 안티 패턴에 대한 모범 사례를 제공합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/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];
}>();
  • 템플릿에서는 케밥 케이스 사용 (@update:model-value).
  • 스크립트에서는 카멜 케이스 사용 (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 Mixins

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

  • Mixins: 불투명한 데이터 흐름, 소스 충돌, 이름 충돌.
  • 컴포저블: 명시적인 임포트, 명확한 반환 값, 구성 가능하고 트리 쉐이킹 가능.

4. 상태 관리

무엇을 언제 사용할지

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

Pinia 셋업 스토어 (권장)

// 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 };
});
  • 셋업 스토어 구문 (옵션 스토어 아님)을 사용하세요.
  • 비즈니스 수준의 변이에는 actions를, 그룹화된 업데이트에는 $patch()를 선호하세요.
  • 모든 비동기 action: 로딩 + 성공 + 오류를 처리하세요.

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: "앨리스" } },
  });
  expect(wrapper.text()).toContain("앨리스");
  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);
  // ... 가져오기 및 반환
});

런타임 설정

// 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());
  // ... signal을 사용하여 가져오기
});

useId()

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

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

defer Teleport

<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 key = index재정렬 시 상태 손상안정적인 데이터베이스 ID 사용
props 변이단방향 데이터 흐름 위반이벤트 발생 또는 v-model 사용
사용자 콘텐츠에 v-html 사용XSS 취약점DOMPurify로 살균
Vue 3에서 Mixins 사용불투명하고 충돌 가능성컴포저블로 대체
컴포저블의 모듈 범위 부수 효과인스턴스 간 공유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/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

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.

관련 스킬