agentsclimarketplace

Nuxt standards

Skill chawkitariq/fidely/.claude/skills/nuxt-standards

PWA de fidélité pour commerces, 100% offline, sans backend, sans compte. Deux modes : Commerçant (gestion des cartes et scan client) et Client (QR code personnel).

Install
npx -y skills add chawkitariq/fidely --skill nuxt-standards

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Structure fichiers et conventions Nuxt 4 pour Fidely (dossier app/, pages/, components/, composables/). Déclencher quand : ajout/déplacement de fichier, création de layout, page Nuxt, SSR safety, routing (navigateTo, useRoute), pattern ClientOnly, dynamic import html5-qrcode/qrcode, performance. Mots-clés : app/, pages/, layouts/, components/, useDB, SSR, onMounted, ClientOnly, NuxtLink, navigateTo, definePageMeta, html5-qrcode, qrcode, onBeforeUnmount, v-for :key, v-html, UModal, UForm.

SKILL.md

11.7 KB, as published. Nobody here has run it

Skill : Standards & bonnes pratiques Nuxt 3/4

Appliquer ces règles lors de toute création ou modification de code dans le projet.

Ressources disponibles


1. Structure des fichiers (Nuxt 4 — dossier app/)

app/
├── app.vue              # Racine unique — uniquement <UApp> + <NuxtPage /> + useHead/useSeoMeta
├── app.config.ts        # Thème UI uniquement — ne pas y mettre de logique
├── assets/              # CSS, fonts, images importées par Vite
├── components/          # PascalCase.vue — auto-importés
├── composables/         # useXxx.ts — auto-importés
├── layouts/             # default.vue + layouts nommés — wrappent <NuxtPage />
├── pages/               # kebab-case.vue ou [param].vue — routing automatique
└── utils/               # camelCase.ts — auto-importés

Conventions de nommage :

  • Composants : PascalCase.vue (ex: CardItem.vue)
  • Composables : camelCase préfixé use (ex: useDB.ts, useClient.ts)
  • Pages : kebab-case.vue ou [param].vue pour les routes dynamiques
  • Utils : camelCase.ts
  • Icônes : toujours i-lucide-* (Lucide en priorité)

Règles absolues :

  • Un fichier = une responsabilité
  • Jamais de logique métier dans app.vue ou app.config.ts
  • Jamais de composant dans pages/ — extraire dans components/
  • server/ uniquement si une API route SSR est délibérément ajoutée

1b. TypeScript, ESLint, JSDoc, structure composants & composables

Ces règles sont centralisées dans le skill coding-standards — s'y référer pour :

  • TypeScript strict (zéro any, import type, retours typés)
  • ESLint / formatage
  • JSDoc obligatoire sur toutes les fonctions et interfaces exportées
  • Structure <script setup> et ordre des blocs
  • Conventions composables (useXxx, guards SSR, auto-imports)

2. Layouts (app/layouts/)

Quand utiliser un layout

SituationSolution
Toutes les pages partagent le même chrome (header, footer, nav)layouts/default.vue
Certaines pages ont un chrome différent (ex: plein écran vs avec nav)Layout nommé (layouts/fullscreen.vue)
Une seule page a un comportement uniquedefinePageMeta sur la page uniquement
App simple sans chrome global répétéPas de layout — app.vue suffit

Projet Fidely : pas de layout actuellement (app.vue = <UApp><NuxtPage /></UApp>). Créer un layout si du chrome commun (ex: bottom nav, header Pro) doit apparaître sur plusieurs pages.

Créer un layout

<!-- app/layouts/default.vue -->
<template>
  <div class="min-h-screen flex flex-col">
    <slot />   <!-- NuxtPage s'insère ici -->
  </div>
</template>
<!-- app/layouts/merchant.vue — layout pour les pages Pro -->
<script setup lang="ts">
function switchToClient() {
  localStorage.setItem('fidely_mode', 'client')
  navigateTo('/me')
}
</script>

<template>
  <div class="min-h-screen flex flex-col">
    <!-- Header commun à toutes les pages Pro -->
    <div class="flex items-center justify-between px-4 py-4 border-b border-default">
      <h1 class="text-2xl font-bold text-primary">Fidely Pro</h1>
      <UButton
        icon="i-lucide-user-circle"
        color="neutral"
        variant="ghost"
        size="lg"
        @click="switchToClient"
      />
    </div>
    <slot />
  </div>
</template>

Activer un layout dans une page

<!-- Méthode 1 : definePageMeta (recommandée — statique, analysable) -->
<script setup lang="ts">
definePageMeta({ layout: 'merchant' })
</script>

<!-- Méthode 2 : layout dynamique (si le layout dépend d'une condition runtime) -->
<script setup lang="ts">
const { layout } = useRoute().meta  // récupéré depuis un middleware si besoin
</script>
<template>
  <NuxtLayout name="merchant">
    <!-- contenu -->
  </NuxtLayout>
</template>

<!-- Méthode 3 : désactiver le layout default sur une page spécifique -->
<script setup lang="ts">
definePageMeta({ layout: false })
</script>

Règles layouts

  • Nommage : kebab-case.vue → activé avec layout: 'nom-kebab'
  • default.vue s'applique automatiquement à toutes les pages sans definePageMeta
  • Ne pas dupliquer la logique de switch de mode dans chaque page si un layout peut la centraliser
  • <slot /> est obligatoire — sans lui, le contenu de la page n'est pas rendu
  • Pas de logique métier lourde dans un layout — uniquement chrome UI + navigation
  • useHead() dans le layout : possible pour les meta communes, mais préférer useSeoMeta page par page pour la flexibilité
  • Layouts et <ClientOnly> : si le layout contient des composants browser-only, les wrapper dans <ClientOnly>

Middleware de layout (cas avancé)

// app/middleware/mode.ts — redirige selon le mode mémorisé
export default defineNuxtRouteMiddleware(() => {
  if (import.meta.server) return
  const mode = localStorage.getItem('fidely_mode')
  // logique de redirection
})
<!-- Activer un middleware sur une page -->
<script setup lang="ts">
definePageMeta({ middleware: 'mode' })
</script>

3. Pages (app/pages/)

<!-- ✅ Correct -->
<script setup lang="ts">
const route = useRoute()
const toast = useToast()
const { getAllCards } = useDB()   // logique dans composable

const cards = ref<Card[]>([])

onMounted(async () => {
  cards.value = await getAllCards()
})
</script>

<template>
  <div class="min-h-screen flex flex-col">
    <!-- contenu -->
  </div>
</template>

Ce qu'une page NE doit PAS contenir :

  • Logique IndexedDB directe (→ composable)
  • Logique de validation (→ utils/validators.ts)
  • Composants inline définis dans <script> (→ components/)
  • Plus de ~150 lignes de template (→ découper en composants)

4. Composants (app/components/)

Structure détaillée dans coding-standards (ordre des blocs, props, emits, computed, JSDoc).

Ce qu'un composant NE doit PAS contenir :

  • Logique IndexedDB directe (→ composable)
  • Logique de validation (→ utils/validators.ts)
  • Composants inline définis dans <script> (→ components/)
  • Plus de ~150 lignes de template (→ découper en composants)

5. Composables (app/composables/)

Pattern complet, JSDoc et guards SSR dans coding-standards.

  • useDB() est le seul point d'entrée pour IndexedDB — ne jamais appeler indexedDB directement dans une page ou un composant

6. SSR Safety — règles strictes

APIRègle
indexedDBToujours dans onMounted ou derrière if (import.meta.server) return
localStorageToujours dans onMounted — jamais au top-level du <script setup>
document / windowToujours dans onMounted ou <ClientOnly>
html5-qrcodeawait import('html5-qrcode') dans onMounted uniquement
qrcodeawait import('qrcode') dans onMounted + composant dans <ClientOnly>
crypto.randomUUID() / uuidv7()Sûr côté serveur et client
<!-- ✅ ClientOnly pour les composants browser-only -->
<ClientOnly>
  <Scanner @scan="onScan" />
  <template #fallback>
    <div class="h-64 bg-gray-100 animate-pulse rounded-xl" />
  </template>
</ClientOnly>

7. Auto-imports Nuxt — ne jamais importer manuellement

Voir coding-standards pour les règles complètes.

Auto-importés : ref, computed, reactive, watch, watchEffect, onMounted, onBeforeUnmount, useRoute, useRouter, navigateTo, useToast, useHead, useSeoMeta, et tous les composables/utils du dossier app/


8. Nuxt UI v4 — patterns corrects

Formulaires

<!-- ✅ UForm avec validate function (Zod) -->
<UForm :state="state" :validate="validate" @submit="onSubmit">
  <UFormField name="title" label="Titre">   <!-- name = clé dans state -->
    <UInput v-model="state.title" size="lg" class="w-full" />
  </UFormField>
  <UButton type="submit" color="primary" :loading="isSaving">
    Enregistrer
  </UButton>
</UForm>

Toasts — couleurs valides uniquement

// ✅ couleurs valides
toast.add({ title: 'OK', color: 'success' })
toast.add({ title: 'Erreur', color: 'error' })
toast.add({ title: 'Info', color: 'primary' })
// ❌ ne pas utiliser 'green', 'red', 'blue' — tomberont en 'neutral'

Modales

<!-- ✅ v-model:open (pas v-model) -->
<UModal v-model:open="isOpen" title="Confirmation">
  <template #body>Voulez-vous supprimer cette carte ?</template>
  <template #footer>
    <UButton color="neutral" variant="outline" @click="isOpen = false">Annuler</UButton>
    <UButton color="error" :loading="isDeleting" @click="confirm">Supprimer</UButton>
  </template>
</UModal>

Loading states — obligatoire sur tout bouton async

<!-- ✅ bouton avec état de chargement -->
<UButton
  color="primary"
  :loading="isSaving"
  :disabled="isSaving"
  @click="save"
>
  Enregistrer
</UButton>

9. Routing Nuxt

// Navigation programmatique
await navigateTo('/merchant')              // ✅ helper Nuxt auto-importé
await navigateTo(`/client/${clientId}`)   // ✅ route dynamique

// Lecture des params
const route = useRoute()
const id = route.params.id as string      // ✅ typer explicitement

// Liens dans les templates
<NuxtLink to="/merchant">Retour</NuxtLink>  // ✅ préférer aux <a>
<UButton to="/scan">Scanner</UButton>       // ✅ UButton supporte la prop to

10. Performance & bonnes pratiques

  • Pas de watch inutile — préférer computed quand la valeur est dérivée de props/state
  • Cleanup obligatoire dans onBeforeUnmount pour les listeners, timers, et instances browser (ex: scanner.clear())
  • Dynamic imports pour les libs lourdes uniquement chargées côté client :
    const { Html5QrcodeScanner } = await import('html5-qrcode')
    const QRCode = await import('qrcode')
    
  • Pas de v-for sans :key — la clé doit être un identifiant stable (l'id de l'objet, jamais l'index)
  • v-html interdit sur du contenu utilisateur (XSS)

11. Checklist avant de marquer une tâche ✅

Checklist complète (TypeScript, JSDoc, ESLint, tests) dans coding-standards.

Points Nuxt spécifiques :

[ ] Guards SSR en place sur toutes les browser APIs (import.meta.server)
[ ] Composable useDB() utilisé pour tout accès IndexedDB (jamais indexedDB direct)
[ ] Loading states sur tous les boutons async (:loading + :disabled)
[ ] Confirmation UModal avant toute action destructive
[ ] Validation Zod avant toute persistance de formulaire
[ ] Icônes Lucide (i-lucide-*) utilisées en priorité
[ ] Pas de dark mode introduit (classes dark:, useColorMode)
[ ] Cleanup onBeforeUnmount si ressource browser ouverte (scanner.clear())
[ ] Dynamic imports pour html5-qrcode et qrcode dans onMounted uniquement
[ ] Pas de routeRules prerender — l'app dépend de IndexedDB/localStorage au montage

Confirmation en fin de tâche

nuxt-standards appliqués

  • Structure fichiers : [conforme app/]
  • SSR safety : [guards en place / ClientOnly utilisé]
  • Nuxt UI : [composants sémantiques utilisés]
  • Performance : [cleanup / dynamic imports]

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.