💻 Développement

dev-vue-guide

Développement d'applications Vue.js 3 avec Composition API, Pinia, Vue Router, composants réactifs et bonnes pratiques.

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- dev-vue-guide --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } dev-vue-guide -Launch"

🚀 Déjà installé ?

claude "/dev-vue-guide"

Ou tapez /dev-vue-guide dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

VueVue.jsComposition APIPiniaVue Routercomposant Vue

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/dev-vue-guide ~/.claude/skills/

Payload du plugin : skills/dev-vue-guide · source éditable : dev-skills/vue-guide

📖 Manuel

Guide Vue.js 3

1. Scaffolding du projet

# Nouveau projet Vite + Vue 3 + TS
npm create vite@latest mon-app -- --template vue-ts
cd mon-app && npm install

# Ajout immédiat des dépendances core
npm install pinia vue-router@4
npm install -D vitest @vue/test-utils jsdom

Arborescence cible :

src/
  components/   # composants réutilisables (atomiques)
  views/        # pages associées à une route
  composables/  # logique partagée (use*)
  stores/       # stores Pinia
  router/       # index.ts + guards
  types/        # interfaces TS globales

2. Composants — Script Setup + TypeScript

Toujours <script setup lang="ts">. Ne jamais utiliser l'Options API dans du code nouveau.

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

interface Props {
  title: string
  count?: number
}

const props = withDefaults(defineProps<Props>(), { count: 0 })
const emit = defineEmits<{ increment: [value: number] }>()

const doubled = computed(() => props.count * 2)

function handleClick() {
  emit('increment', props.count + 1)
}
</script>

<template>
  <button @click="handleClick">{{ title }} — {{ doubled }}</button>
</template>

Critères ref vs reactive :

CasOutil
Valeur primitive ou objet unique interchangeableref()
Objet complexe multi-propriétésreactive()
Gros objet immuable côté UIshallowRef()
Prop réactive transmise à un composabletoRef(props, 'key')

3. Pinia — Gestion d'état

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

export const useUserStore = defineStore('user', () => {
  const name = ref('')
  const isAdmin = computed(() => name.value.startsWith('admin_'))

  async function fetchUser(id: string) {
    const data = await api.get(`/users/${id}`)
    name.value = data.name
  }

  return { name, isAdmin, fetchUser }
})
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useUserStore } from '@/stores/user'

const store = useUserStore()
const { name, isAdmin } = storeToRefs(store)  // réactivité conservée
// store.fetchUser — actions appelées directement, sans storeToRefs
</script>

Persistance (pinia-plugin-persistedstate) :

// main.ts
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
pinia.use(piniaPluginPersistedstate)

// dans le store : ajouter { persist: true } comme 3e argument de defineStore

4. Vue Router — Configuration et Guards

// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/dashboard',
      component: () => import('@/views/Dashboard.vue'), // lazy load obligatoire
      meta: { requiresAuth: true },
    },
    {
      path: '/user/:id',
      component: () => import('@/views/UserProfile.vue'),
      props: true,  // injecte :id comme prop
    },
  ],
})

router.beforeEach((to) => {
  const auth = useAuthStore()
  if (to.meta.requiresAuth && !auth.isLoggedIn) {
    return { name: 'Login', query: { redirect: to.fullPath } }
  }
})

5. Composables — Extraction de logique

Convention : fichier src/composables/useXxx.ts, retour d'un objet de refs.

// src/composables/useFetch.ts
import { ref } from 'vue'

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

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

  return { data, error, loading, execute }
}

6. Réactivité avancée

// watch avec cleanup
watch(userId, async (newId, _, onCleanup) => {
  const controller = new AbortController()
  onCleanup(() => controller.abort())
  data.value = await fetchUser(newId, controller.signal)
})

// watchEffect — recalcul automatique sur toute dépendance lue
watchEffect(() => {
  document.title = `${route.name} | ${appName.value}`
})

// provide / inject (typage fort)
const ThemeKey: InjectionKey<Ref<string>> = Symbol('theme')
provide(ThemeKey, ref('dark'))
const theme = inject(ThemeKey)  // Ref<string> | undefined

7. Performance et code splitting

// Composant asynchrone avec loader/erreur
const HeavyChart = defineAsyncComponent({
  loader: () => import('@/components/HeavyChart.vue'),
  loadingComponent: Spinner,
  errorComponent: ErrorMsg,
  delay: 200,
  timeout: 5000,
})

8. Tests avec Vitest + Vue Test Utils

// src/components/__tests__/Counter.spec.ts
import { mount } from '@vue/test-utils'
import Counter from '../Counter.vue'

test('émet increment au clic', async () => {
  const wrapper = mount(Counter, { props: { count: 3 } })
  await wrapper.find('button').trigger('click')
  expect(wrapper.emitted('increment')?.[0]).toEqual([4])
})
npx vitest run          # one-shot CI
npx vitest --ui         # interface graphique

Garde-fous et anti-patterns

ProblèmeSymptômeCorrection
Déstructuration directe d'un storeperte de réactivitéstoreToRefs()
Mutation directe du state Pinia hors actionincohérencepasser par une action
Utilisation des mixinsconflits, opacitécomposables use*
reactive() sur une refdouble-wrappingutiliser ref() seul
Props muables localementviolation one-wayemit + event handler côté parent
Import de composants lourds sans lazybundle trop grosdefineAsyncComponent ou () => import()
watch sans nettoyage sur fetchrace conditiononCleanup + AbortController
v-for sans :key stablererenders erratiquestoujours :key sur id métier

Checklist qualité (2026)