Source profileQuality 84/100

affaan-m/ECC/skills/vue-patterns/SKILL.md

vue-patterns

Vue.js 3 Composition API patterns, component architecture, reactivity best practices, Pinia state management, Vue Router navigation, and Nuxt SSR patterns. Activates for Vue, Nuxt, Vite, or Pinia projects.

Source repository stars
234,327
Declared platforms
0
Static risk flags
1
Last source update
2026-07-27
Source checked
2026-07-28

Decision brief

What it does—and where it fits

Comprehensive guide for Vue.js 3 development using Composition API (), covering component design, reactivity, state management, routing, testing, and SSR patterns. Nuxt-specific guidance is included where it differs from vanilla Vue.

Best for

    Not for

    • Tasks that require unconfirmed production actions or broad system permissions.
    • Environments where the pinned source and install steps cannot be inspected.

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorNot declaredNo explicit evidencePortability before use
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/affaan-m/ECC --skill "skills/vue-patterns"
    Safe inspection promptEditorial

    Inspect the Agent Skill "vue-patterns" from https://github.com/affaan-m/ECC/blob/4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38/skills/vue-patterns/SKILL.md at commit 4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      Pinia Setup Store (Preferred)

      Use Setup Store syntax (not Options Store).

      Use Setup Store syntax (not Options Store).Prefer actions for business-level mutations and $patch() for grouped updates.Every async action: handle loading + success + error.
    2. 02

      When to Activate

      Activate this skill when: - The project uses Vue.js (any version), Nuxt, Vite + Vue, or Pinia. - The user asks about Vue component architecture, composables, reactivity, or state management. - Reviewing Vue Single-File Components (.vue files). - Setting up Vue Router, Pinia stor…

      The project uses Vue.js (any version), Nuxt, Vite + Vue, or Pinia.The user asks about Vue component architecture, composables, reactivity, or state management.Reviewing Vue Single-File Components (.vue files).
    3. 03

      1. Project Structure

      Review the “1. Project Structure” section in the pinned source before continuing.

      Review and apply the “1. Project Structure” source section.
    4. 04

      Recommended Layout (Feature-First)

      Review the “Recommended Layout (Feature-First)” section in the pinned source before continuing.

      Review and apply the “Recommended Layout (Feature-First)” source section.
    5. 05

      File Naming

      Review the “File Naming” section in the pinned source before continuing.

      Review and apply the “File Naming” source section.

    Permission review

    Static risk signals and limitations

    Network access

    medium · line 329

    The documentation includes network, browsing, or remote request actions.

    () => $fetch(`/api/users/${id}`),

    Network access

    medium · line 360

    The documentation includes network, browsing, or remote request actions.

    apiBase: "https://api.example.com",

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score84/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars234,327SourceRepository attention, not individual Skill quality
    Compatibility0 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    affaan-m/ECC
    Skill path
    skills/vue-patterns/SKILL.md
    Commit
    4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38
    License
    MIT
    Collected
    2026-07-28
    Default branch
    main
    View the original SKILL.md

    Vue.js Patterns and Best Practices

    Comprehensive guide for Vue.js 3 development using Composition API (<script setup>), covering component design, reactivity, state management, routing, testing, and SSR patterns. Nuxt-specific guidance is included where it differs from vanilla Vue.

    When to Activate

    Activate this skill when:

    • The project uses Vue.js (any version), Nuxt, Vite + Vue, or Pinia.
    • The user asks about Vue component architecture, composables, reactivity, or state management.
    • Reviewing Vue Single-File Components (.vue files).
    • Setting up Vue Router, Pinia stores, or Vite/Vitest configuration.
    • Discussing Vue-specific performance, security, or SSR patterns.

    1. Project Structure

    Recommended Layout (Feature-First)

    src/
    ├── api/              # API client and endpoint definitions
    ├── assets/           # Static assets (images, fonts, icons)
    ├── components/       # Shared/reusable components
    │   ├── base/         # Base UI primitives (Button, Input, Modal)
    │   └── features/     # Feature-specific shared components
    ├── composables/      # Reusable Composition API logic
    ├── layouts/          # Page layouts (optional)
    ├── pages/            # Route-level page components
    ├── router/           # Vue Router configuration
    ├── stores/           # Pinia stores
    ├── types/            # TypeScript type definitions
    ├── utils/            # Pure utility functions
    └── App.vue           # Root component
    

    File Naming

    ConventionWhen to Use
    PascalCase.vueAll components (enforced by vue/multi-word-component-names)
    useCamelCase.tsComposables
    camelCase.tsUtilities, API clients, types
    kebab-case directoriesRoute segments, feature folders

    2. Component Architecture

    Single-File Component Order

    <script setup lang="ts">
    // 1. Imports (vue → ecosystem → absolute → relative)
    // 2. Props & Emits & Slots
    // 3. Composables
    // 4. Local state (ref/reactive)
    // 5. Computed properties
    // 6. Methods
    // 7. Watchers
    // 8. Lifecycle hooks
    </script>
    
    <template>
      <!-- Template content -->
    </template>
    
    <style scoped>
      /* Scoped styles */
    </style>
    

    Presentational vs Container

    • Container components: Own data fetching, state, and side effects. Render presentational components.
    • Presentational components: Receive props, emit events. No API calls, no store access. Pure rendering.

    Props Best Practices

    // Type-based props with defaults
    interface Props {
      label: string;
      variant?: "primary" | "secondary";
      disabled?: boolean;
      items: Item[];
    }
    
    const props = withDefaults(defineProps<Props>(), {
      variant: "primary",
      disabled: false,
    });
    
    • Always provide type, and required/default where appropriate.
    • Boolean props: isXxx, hasXxx, canXxx.
    • Never mutate props — emit events instead.
    • For v-model binding, use defineModel() (Vue 3.4+) or modelValue + update:modelValue.

    Events

    const emit = defineEmits<{
      submit: [];
      "update:modelValue": [value: string];
      select: [id: string, index: number];
    }>();
    
    • Use kebab-case in templates (@update:model-value).
    • Use camelCase in script (emit("update:modelValue", val)).

    3. Composables (Reusable Logic)

    Structure

    // 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);
    }
    

    Rules

    • Must start with use prefix.
    • Return reactive values (ref, computed, reactive), never plain primitives.
    • Accept reactive inputs via MaybeRef / toRef() / toValue().
    • Clean up side effects in onUnmounted or watcher onCleanup.
    • No module-scope side effects.

    vs Mixins

    Composables replace Vue 2 mixins entirely:

    • Mixins: Opaque data flow, source-of-truth collisions, name conflicts.
    • Composables: Explicit imports, clear return values, composable and tree-shakable.

    4. State Management

    When to Use What

    PatternUse Case
    ref() / reactive()Local component state
    Props + EmitsParent-child communication
    Provide / InjectTheme, config, plugin API
    Pinia storeGlobal, shared, complex state
    Server state composableAPI data with caching (wrap fetch/TanStack Query)

    Pinia Setup Store (Preferred)

    // 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 };
    });
    
    • Use Setup Store syntax (not Options Store).
    • Prefer actions for business-level mutations and $patch() for grouped updates.
    • Every async action: handle loading + success + error.

    5. Vue Router

    Route Definitions

    const routes = [
      {
        path: "/users/:id",
        name: "user-detail",
        component: () => import("@/pages/UserDetail.vue"), // lazy
        props: true, // pass params as props
        meta: { requiresAuth: true },
      },
    ];
    

    Navigation Guards

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

    Reactive Route Params

    When a component stays mounted but route params change:

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

    6. Template Patterns

    Template Syntax

    <!-- v-if/v-else-if/v-else -->
    <div v-if="isLoading">Loading...</div>
    <div v-else-if="error">Error: {{ error }}</div>
    <div v-else>{{ content }}</div>
    
    <!-- v-show for frequent toggles -->
    <div v-show="isOpen">Toggled content</div>
    
    <!-- v-for with stable keys -->
    <div v-for="item in items" :key="item.id">{{ item.name }}</div>
    
    <!-- Computed filtered list (not v-if + v-for on same element) -->
    <div v-for="item in activeItems" :key="item.id">{{ item.name }}</div>
    
    <!-- Event handling -->
    <form @submit.prevent="handleSubmit">
      <button type="submit">Save</button>
    </form>
    
    <!-- v-model -->
    <input v-model="name" />
    <CustomInput v-model="value" v-model:title="title" />
    

    7. Performance

    TechniqueWhen to Use
    v-memoList items that rarely change
    v-onceContent rendered once and static forever
    shallowRef()Large data structures replaced wholesale
    shallowReactive()Only top-level properties are reactive
    v-show over v-ifFrequent visibility toggles
    <KeepAlive :max="10">Cache toggled views
    Lazy routes() => import(...) for non-critical routes
    SuspenseAsync component loading with fallback

    8. Testing

    Stack

    • Vitest for unit and component tests
    • Vue Test Utils for mounting and interaction
    • @pinia/testing for store mocking
    • Playwright for E2E

    Component Test Pattern

    import { mount } from "@vue/test-utils";
    import { createPinia, setActivePinia } from "pinia";
    import UserCard from "./UserCard.vue";
    
    beforeEach(() => { setActivePinia(createPinia()); });
    
    it("renders and emits", 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-Specific Patterns

    Auto-Imports

    Nuxt auto-imports ref, computed, watch, useFetch, useAsyncData, etc. Use them directly without importing. For non-Nuxt projects, always import explicitly.

    useAsyncData / useFetch

    const { data: user, pending, error, refresh } = await useAsyncData(
      "user", // unique key for caching
      () => $fetch(`/api/users/${id}`),
    );
    
    const { data: posts } = await useFetch("/api/posts", {
      query: { page: 1 },
      key: "posts-page-1", // dedupes requests
    });
    

    Server Routes

    // 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
    });
    

    Runtime Config

    // nuxt.config.ts
    export default defineNuxtConfig({
      runtimeConfig: {
        // server-only
        apiSecret: "",
        // public (exposed to client)
        public: {
          apiBase: "https://api.example.com",
        },
      },
    });
    

    10. Vue 3.5+ New APIs

    Reactive Props Destructure

    Vue 3.5 stabilized reactive props destructure — destructured variables from defineProps() are automatically reactive:

    // Vue 3.5+: destructured props are reactive (no need for toRefs)
    const { count = 0, msg = "hello" } = defineProps<{
      count?: number;
      msg?: string;
    }>();
    
    // Limitation: cannot watch destructured prop directly
    watch(() => count, (newVal) => { ... }); // PASS getter required
    

    useTemplateRef()

    Replace name-matched plain refs with useTemplateRef() for template references:

    import { useTemplateRef } from "vue";
    const inputEl = useTemplateRef<HTMLInputElement>("input");
    // "input" matches the ref="input" attribute in template, not the variable name
    

    Supports dynamic ref IDs: useTemplateRef(dynamicRefId).

    onWatcherCleanup()

    Globally importable watcher cleanup API (Vue 3.5+). It must be called synchronously inside the watcher callback:

    import { watch, onWatcherCleanup } from "vue";
    
    watch(userId, async (newId) => {
      const controller = new AbortController();
      onWatcherCleanup(() => controller.abort());
      // ... fetch with signal
    });
    

    useId()

    SSR-stable unique ID generation for form elements and accessibility:

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

    defer Teleport

    <Teleport defer> allows teleporting to targets rendered in the same cycle:

    <Teleport defer to="#container">Content</Teleport>
    <div id="container"></div>
    

    Lazy Hydration (SSR)

    defineAsyncComponent() now supports hydrate strategy:

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

    Anti-Patterns

    Anti-PatternWhy It's WrongThe Fix
    Destructuring defineProps() (Vue < 3.5)Captures snapshot, loses reactivityAccess via props.xxx or use toRefs()
    watch() on destructured prop (Vue 3.5+)Compile-time error — destructured props can't be watched directlyUse getter wrapper: watch(() => count, ...)
    v-if + v-for on same elementAmbiguous execution orderUse computed filtered array
    v-for key = indexBroken state on reorderUse stable database IDs
    Mutating propsViolates one-way data flowEmit events or use v-model
    v-html with user contentXSS vulnerabilitySanitize with DOMPurify
    Mixins in Vue 3Opaque, collision-proneReplace with composables
    Module-scope side effects in composableShared across instancesScope in onMounted + onUnmounted
    reactive() for replaceable stateReplacement breaks reactivityUse ref() instead
    Watcher without cleanupMemory leaks, race conditionsUse onCleanup or onWatcherCleanup() (Vue 3.5+)
    Options API in new Vue 3 codeEcosystem move to Composition APIUse <script setup>
    Plain ref for template referencesNo dynamic ref support, name-matching fragileUse useTemplateRef() (Vue 3.5+)

    Related Skills

    • accessibility — ARIA, semantic HTML, focus management
    • frontend-patterns — Cross-framework frontend architecture
    • typescript — TypeScript best practices applied to Vue projects
    • coding-standards — General code quality standards

    Alternatives

    Compare before choosing