Claude Code pour Vue 3 : guide pratique TypeScript et Pinia
Utiliser Claude Code avec Vue 3, TypeScript, Pinia, composables, formulaires et tests Vitest en projet réel.
Claude Code devient vraiment utile en développement Vue 3 lorsqu’il sert à travailler dans un dépôt existant, pas seulement à générer un composant isolé. Dans un vrai projet, la valeur vient souvent de la lecture d’un SFC, du maintien des types TypeScript, de l’extraction d’une logique réutilisable, de la séparation de l’état dans Pinia et de l’ajout de tests Vitest avant livraison.
Vue est souple, et cette souplesse peut rendre les revues difficiles. Un même codebase peut contenir Options API, Composition API, script setup, Pinia, d’anciennes habitudes Vuex, des conventions Nuxt et des helpers locaux. Si le prompt reste vague, Claude Code peut produire du code qui fonctionne mais qui ne respecte pas les limites du projet.
Ce guide couvre cinq cas d’usage avec Claude Code + Vue 3 + TypeScript : modifier un composant Vue existant, créer un formulaire typé, organiser l’état avec Pinia, extraire un composable et ajouter des tests Vitest. Pour les références de base, consultez la documentation officielle Vue TypeScript avec Composition API, Pinia et Vitest. Pour compléter, lisez aussi les astuces TypeScript et le guide de stratégie de test.
Hypothèses du projet
Le scénario vise une application Vue 3 avec TypeScript, Vite, Pinia et Vitest. Il convient aux outils internes, back offices, systèmes de réservation, workflows d’approbation et interfaces de support. Dans ces applications, les difficultés réelles sont les formulaires, les filtres, la pagination, l’état partagé et les tests.
Avant de demander du code, donnez à Claude Code un harness, c’est-à-dire un cadre de travail sûr pour l’agent. Ce cadre précise les fichiers autorisés, les conventions, les interdictions, les commandes de vérification et les points à expliquer en fin de tâche. Sans cela, l’outil peut ajouter any, modifier des textes d’interface ou déplacer la logique dans la mauvaise couche.
| Situation | Tâche adaptée à Claude Code | Décision humaine |
|---|---|---|
| Refactor Vue existant | Lire le SFC, repérer la duplication, renforcer les types | Compatibilité UI et risque de release |
| Formulaire | État local, validation, soumission | Messages et contrat API |
| Pinia | Types, getters, actions, tests | Persistance et responsabilités |
| Composable | Filtres, pagination, logique réutilisable | Réutilisation réelle ou non |
| Tests | Cas nominaux, erreurs, régressions | Niveau de couverture |
Vue d’ensemble
Quand les exemples grandissent, la question n’est pas seulement de savoir si Claude Code peut écrire le code. Il faut surtout savoir où placer chaque responsabilité. Une séparation claire rend le diff généré plus facile à relire.
flowchart LR
A[Vue SFC] --> B[Composable]
A --> C[Pinia Store]
B --> D[Vitest]
C --> D
E[Claude Code Prompt] --> A
E --> B
E --> C
E --> D
Le SFC garde le rendu et les événements utilisateur. Le composable contient la logique réactive réutilisable, comme les filtres et la pagination. Le store Pinia contient l’état partagé. Vitest protège les comportements à ne pas casser.
Créer une base Vue 3 + TypeScript
Pour une expérimentation, commencez par le scaffold officiel. Pour un dépôt existant, vérifiez d’abord que le typecheck et les tests actuels passent afin d’évaluer clairement l’impact de Claude Code.
npm create vue@latest support-desk-vue -- --typescript --router --pinia --vitest
cd support-desk-vue
npm install
npm run dev
Évitez le prompt “crée une application Vue moderne”. Précisez le gestionnaire de paquets, la présence de Vue Router, l’usage de Pinia et la commande de test. Dans un vrai projet, demandez d’abord à Claude Code de lire package.json, vite.config.ts, tsconfig.app.json, src/components et src/stores.
Cas 1 : formulaire SFC typé
Un formulaire est un bon premier cas, car il regroupe v-model, props, emit, validation, état de sauvegarde et action de store. L’exemple suivant peut être placé dans src/components/TicketForm.vue. Il ne modifie pas les props, garde les valeurs éditables dans des ref locaux et émet l’id après la sauvegarde.
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useTicketStore, type TicketPriority } from '@/stores/ticketStore';
const props = withDefaults(
defineProps<{
initialTitle?: string;
defaultPriority?: TicketPriority;
}>(),
{
initialTitle: '',
defaultPriority: 'medium',
},
);
const emit = defineEmits<{
submitted: [id: string];
}>();
const store = useTicketStore();
const title = ref(props.initialTitle);
const description = ref('');
const priority = ref<TicketPriority>(props.defaultPriority);
const touched = ref(false);
const titleError = computed(() => {
if (!touched.value) return '';
if (title.value.trim().length < 5) return 'Use at least 5 characters.';
return '';
});
const descriptionError = computed(() => {
if (!touched.value) return '';
if (description.value.trim().length < 20) return 'Use at least 20 characters.';
return '';
});
const canSubmit = computed(() => {
return (
title.value.trim().length >= 5 &&
description.value.trim().length >= 20 &&
!store.isSaving
);
});
async function submit() {
touched.value = true;
if (!canSubmit.value) return;
const ticket = await store.saveTicket({
title: title.value.trim(),
description: description.value.trim(),
priority: priority.value,
});
title.value = '';
description.value = '';
priority.value = props.defaultPriority;
touched.value = false;
emit('submitted', ticket.id);
}
</script>
<template>
<form class="ticket-form" @submit.prevent="submit">
<label>
Title
<input v-model="title" name="title" @blur="touched = true" />
</label>
<p v-if="titleError" class="error">{{ titleError }}</p>
<label>
Description
<textarea v-model="description" name="description" @blur="touched = true" />
</label>
<p v-if="descriptionError" class="error">{{ descriptionError }}</p>
<label>
Priority
<select v-model="priority" name="priority">
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
</label>
<button type="submit" :disabled="!canSubmit">
{{ store.isSaving ? 'Saving...' : 'Create ticket' }}
</button>
</form>
</template>
Dans le prompt, ajoutez les contraintes : ne pas muter les props, ne pas introduire any, ne pas ajouter de bibliothèque UI, ne pas mettre les détails API dans le composant. Ces contraintes réduisent fortement le nettoyage manuel.
Cas 2 : état partagé avec Pinia
Si plusieurs écrans utilisent la liste des tickets, le formulaire ne doit pas porter tout l’état. Pinia permet de séparer la liste, le statut de sauvegarde, les getters et les actions.
import { computed, ref } from 'vue';
import { defineStore } from 'pinia';
export type TicketPriority = 'low' | 'medium' | 'high';
export type TicketStatus = 'open' | 'closed';
export interface Ticket {
id: string;
title: string;
description: string;
priority: TicketPriority;
status: TicketStatus;
createdAt: string;
}
export type NewTicketInput = Pick<Ticket, 'title' | 'description' | 'priority'>;
export const useTicketStore = defineStore('tickets', () => {
const tickets = ref<Ticket[]>([]);
const isSaving = ref(false);
const openTickets = computed(() => {
return tickets.value.filter((ticket) => ticket.status === 'open');
});
function addTicket(input: NewTicketInput) {
const ticket: Ticket = {
id: crypto.randomUUID(),
...input,
status: 'open',
createdAt: new Date().toISOString(),
};
tickets.value.unshift(ticket);
return ticket;
}
async function saveTicket(input: NewTicketInput) {
isSaving.value = true;
try {
await new Promise((resolve) => setTimeout(resolve, 150));
return addTicket(input);
} finally {
isSaving.value = false;
}
}
function closeTicket(id: string) {
const ticket = tickets.value.find((item) => item.id === id);
if (ticket) ticket.status = 'closed';
}
return {
tickets,
isSaving,
openTickets,
addTicket,
saveTicket,
closeTicket,
};
});
En production, saveTicket appellera un client API typé. Mais il vaut mieux faire valider d’abord la forme du store, puis le réseau, puis la gestion des erreurs. Les revues restent plus petites.
Cas 3 : extraire un composable
Un composable est une fonction réutilisable qui contient de la logique réactive Vue. En termes simples, c’est la logique de l’écran sortie du composant. Recherche, priorité et pagination sont de bons candidats.
import { computed, ref, watch, type Ref } from 'vue';
import type { Ticket, TicketPriority } from '@/stores/ticketStore';
export function useFilteredTickets(tickets: Ref<Ticket[]>) {
const query = ref('');
const selectedPriority = ref<TicketPriority | 'all'>('all');
const currentPage = ref(1);
const pageSize = ref(10);
const filteredTickets = computed(() => {
const normalizedQuery = query.value.trim().toLowerCase();
return tickets.value.filter((ticket) => {
const matchesQuery =
normalizedQuery.length === 0 ||
ticket.title.toLowerCase().includes(normalizedQuery) ||
ticket.description.toLowerCase().includes(normalizedQuery);
const matchesPriority =
selectedPriority.value === 'all' ||
ticket.priority === selectedPriority.value;
return matchesQuery && matchesPriority;
});
});
const totalPages = computed(() => {
return Math.max(1, Math.ceil(filteredTickets.value.length / pageSize.value));
});
const pagedTickets = computed(() => {
const start = (currentPage.value - 1) * pageSize.value;
return filteredTickets.value.slice(start, start + pageSize.value);
});
watch([query, selectedPriority], () => {
currentPage.value = 1;
});
return {
query,
selectedPriority,
currentPage,
pageSize,
filteredTickets,
pagedTickets,
totalPages,
};
}
Ici, watch ne sert qu’à remettre la page à 1 quand les filtres changent. Les valeurs dérivées restent en computed. C’est une règle simple à ajouter au prompt.
Cas 4 : ajouter des tests Vitest
Plus Claude Code accélère l’implémentation, plus les tests de régression comptent. Commencez par le store : il est proche du comportement métier et moins fragile que les tests DOM.
import { createPinia, setActivePinia } from 'pinia';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { useTicketStore } from './ticketStore';
describe('useTicketStore', () => {
beforeEach(() => {
setActivePinia(createPinia());
vi.stubGlobal('crypto', {
randomUUID: () => 'ticket-1',
});
});
it('adds a new open ticket', () => {
const store = useTicketStore();
const ticket = store.addTicket({
title: 'Cannot submit expense form',
description: 'The submit button stays disabled after all fields are filled.',
priority: 'high',
});
expect(ticket.id).toBe('ticket-1');
expect(store.tickets).toHaveLength(1);
expect(store.openTickets).toHaveLength(1);
});
it('closes an existing ticket', () => {
const store = useTicketStore();
const ticket = store.addTicket({
title: 'Profile image is broken',
description: 'The image URL returns 404 on the account settings page.',
priority: 'medium',
});
store.closeTicket(ticket.id);
expect(store.openTickets).toHaveLength(0);
expect(store.tickets[0]?.status).toBe('closed');
});
});
Ne demandez pas “ajoute beaucoup de tests”. Demandez les comportements critiques : entrée trop courte, entrée valide, sauvegarde en cours, succès, échec et changement d’état Pinia.
Prompt pour modifier Vue existant
Sur un code existant, évitez une grosse réécriture. Faites lire le composant, demandez les risques, renforcez les types, extrayez un composable, ajustez Pinia si nécessaire, puis ajoutez les tests.
You are working in a Vue 3 + TypeScript + Pinia project.
Goal:
Refactor the ticket form so validation logic is typed, reusable, and covered by Vitest.
Allowed files:
- src/components/TicketForm.vue
- src/composables/useFilteredTickets.ts
- src/stores/ticketStore.ts
- src/stores/ticketStore.test.ts
Rules:
- Use <script setup lang="ts">.
- Do not mutate props directly.
- Do not introduce any.
- Prefer computed for derived values.
- Use watch only for explicit side effects.
- Keep UI text unchanged unless a validation message is missing.
Verification:
- npm run typecheck
- npm run test:unit
After editing, explain the risk areas and the tests you added.
Ce prompt sépare les décisions techniques que Claude Code peut proposer et les décisions produit que l’équipe doit valider : textes, contrat API, accessibilité et calendrier de livraison.
Pièges fréquents
Premier piège : mélanger Options API et Composition API sans plan. Vue 3 supporte les deux, mais un composant avec data, methods, setup et script setup devient pénible à relire.
Deuxième piège : mal comprendre ref et reactive. ref est souvent plus simple pour les valeurs primitives, les tableaux et les valeurs remplacées. reactive convient aux objets cohérents, mais la destructuration peut surprendre.
Troisième piège : muter les props. Un enfant ne doit pas modifier silencieusement l’état du parent. Utilisez un ref local, un emit ou defineModel si l’équipe a adopté ce modèle.
Quatrième piège : abuser de watch. Une valeur dérivée doit être en computed. Gardez watch pour une synchronisation de route, une remise à zéro de page, un appel API ou une écriture de stockage.
Cinquième piège : laisser les types tomber en any. Claude Code peut ajouter as any pour contourner une erreur. Relisez any, unknown, les assertions et les tableaux vides.
Sixième piège : découper trop de petits SFC. Des composants non réutilisés ajoutent surtout des props et emits. Extrayez d’abord la logique en composables, puis séparez l’UI quand elle a une vraie autonomie.
Septième piège : oublier les tests. Le code généré peut paraître propre tout en ratant les cas limites. Pour un formulaire, testez entrée courte, entrée valide, sauvegarde, succès, échec et mutation du store.
Workflow pratique et CTA
Dans un dépôt réel, ne commencez pas par “construis tout l’écran”. Demandez d’abord une analyse des risques, faites un petit changement, exécutez typecheck et tests, puis continuez. Les petits diffs se relisent mieux.
Pour une équipe, documentez les règles dans CLAUDE.md : Vue en script setup, état partagé dans des setup stores Pinia, valeurs dérivées en computed, effets explicites en watch, vérification par npm run typecheck et npm run test:unit. ClaudeCodeLab peut aider à formaliser ces règles via la formation et consultation Claude Code.
Résumé
Claude Code accélère les refactors Vue 3, les formulaires, Pinia, les composables et les tests. La qualité dépend surtout du cadre donné : fichiers autorisés, style Vue, éléments à ne pas changer et commandes de vérification.
Pour débuter, travaillez sur un SFC, un composable, un store et un fichier de test. Ce cycle court enseigne le modèle mental de Vue tout en produisant du code proche de la production.
Résultat de l’essai
J’ai testé ce flux dans un petit projet Vue 3 + TypeScript. Demander formulaire, store, composable et tests en une seule requête produisait un diff plus grand et plus long à relire. En séparant SFC, Pinia, composable puis Vitest, les problèmes étaient plus faciles à isoler. Les contraintes les plus utiles ont été “ne pas muter les props”, “ne pas introduire any” et “utiliser computed pour les valeurs dérivées”. Le résultat se vérifiait mieux avec npm run typecheck et npm run test:unit.
PDF gratuit: cheatsheet Claude Code
Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.
Nous protégeons vos données et n'envoyons pas de spam.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Workflow Obsidian vers CLAUDE.md avec Claude Code
Transformer des notes Obsidian en notes CLAUDE.md concises pour reprendre les sessions sans réexpliquer.
Claude Code Revenue CTA Routing : relier articles, PDF, Gumroad et consultation
Un workflow Claude Code pour orienter les lecteurs vers PDF gratuit, Gumroad ou consultation selon l'intention.
Règles de handoff Claude Code en équipe: preuves, permissions, rollback et revenus
Un format concret pour transmettre un travail Claude Code avec preuves, permissions, rollback, PDF gratuit, Gumroad et consultation.