building-vue-apps

doanchienthangdev's avatarfrom doanchienthangdev

Builds production Vue 3 applications with Composition API, TypeScript, Pinia, and Vue Router. Use when creating Vue.js SPAs, component libraries, or reactive web interfaces.

⭐0stars🔀0forks📁View on GitHub🕐Updated Jan 8, 2026

When & Why to Use This Skill

This Claude skill streamlines the development of production-ready Vue 3 applications by leveraging modern ecosystem standards including the Composition API, TypeScript, Pinia, and Vue Router. It helps developers build scalable Single Page Applications (SPAs), high-performance reactive interfaces, and reusable component libraries while enforcing industry best practices and type safety.

Use Cases

  • Scaffolding new Vue 3 projects with a standardized directory structure and pre-configured routing and state management.
  • Developing type-safe UI components using TypeScript and the <script setup> syntax to minimize runtime errors.
  • Implementing complex global state management logic using Pinia stores and reusable logic via Composables.
  • Setting up advanced navigation flows with Vue Router, including lazy loading, meta-based guards, and dynamic routing.
  • Creating robust component libraries with well-defined prop interfaces and typed event emitters for enterprise-level consistency.
namebuilding-vue-apps
descriptionBuilds production Vue 3 applications with Composition API, TypeScript, Pinia, and Vue Router. Use when creating Vue.js SPAs, component libraries, or reactive web interfaces.

Vue.js

Quick Start

<script setup lang="ts">
import { ref, computed } from 'vue';

const count = ref(0);
const doubled = computed(() => count.value * 2);
</script>

<template>
  <button @click="count++">
    Count: {{ count }} (doubled: {{ doubled }})
  </button>
</template>

Features

Feature Description Guide
Composition API Reactive state, composables, lifecycle COMPOSITION.md
TypeScript Props, emits, refs typing TYPESCRIPT.md
Pinia State management, stores, plugins PINIA.md
Vue Router Navigation, guards, lazy loading ROUTER.md
Testing Vitest, Vue Test Utils patterns TESTING.md
Performance Lazy loading, memoization, virtual lists PERFORMANCE.md

Common Patterns

Component with Props and Emits

<script setup lang="ts">
interface Props {
  user: { id: string; name: string; email: string };
  showActions?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
  showActions: true,
});

const emit = defineEmits<{
  (e: 'edit', user: Props['user']): void;
  (e: 'delete', id: string): void;
}>();
</script>

<template>
  <div class="user-card">
    <h3>{{ user.name }}</h3>
    <p>{{ user.email }}</p>
    <div v-if="showActions">
      <button @click="emit('edit', user)">Edit</button>
      <button @click="emit('delete', user.id)">Delete</button>
    </div>
  </div>
</template>

Composable for Reusable Logic

// composables/useFetch.ts
import { ref, watch, type Ref } from 'vue';

export function useFetch<T>(url: Ref<string>) {
  const data = ref<T | null>(null);
  const loading = ref(false);
  const error = ref<Error | null>(null);

  async function fetchData() {
    loading.value = true;
    error.value = null;
    try {
      const response = await fetch(url.value);
      data.value = await response.json();
    } catch (e) {
      error.value = e as Error;
    } finally {
      loading.value = false;
    }
  }

  watch(url, fetchData, { immediate: true });

  return { data, loading, error, refetch: fetchData };
}

Pinia Store

// stores/user.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

export const useUserStore = defineStore('user', () => {
  const user = ref<User | null>(null);
  const token = ref<string | null>(null);

  const isAuthenticated = computed(() => !!user.value);

  async function login(credentials: { email: string; password: string }) {
    const res = await authService.login(credentials);
    user.value = res.user;
    token.value = res.token;
  }

  function logout() {
    user.value = null;
    token.value = null;
  }

  return { user, token, isAuthenticated, login, logout };
});

Workflows

Component Development

  1. Define props interface with TypeScript
  2. Define emits with typed events
  3. Use <script setup> syntax
  4. Create composables for reusable logic
  5. Write tests with Vitest + Vue Test Utils

Router Setup

const routes = [
  { path: '/', component: () => import('./views/Home.vue') },
  {
    path: '/dashboard',
    component: () => import('./views/Dashboard.vue'),
    meta: { requiresAuth: true },
  },
];

router.beforeEach((to, from, next) => {
  const userStore = useUserStore();
  if (to.meta.requiresAuth && !userStore.isAuthenticated) {
    next({ path: '/login', query: { redirect: to.fullPath } });
  } else {
    next();
  }
});

Best Practices

Do Avoid
Use Composition API with <script setup> Options API in new code
Type props/emits with interfaces any types
Create composables for shared logic Duplicating reactive logic
Use Pinia for global state Overusing provide/inject
Lazy load routes and components Bundling everything upfront

Project Structure

src/
├── App.vue
├── main.ts
├── components/         # Reusable components
├── composables/        # Composition functions
├── views/              # Page components
├── stores/             # Pinia stores
├── router/             # Route definitions
├── services/           # API services
└── types/              # TypeScript types

For detailed examples and patterns, see reference files above.