agentsclimarketplace

Nuxt standards

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

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.From its SKILL.md

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.

SKILL.md

11.7 KB, ~3.2k tokens by cl100k_base, 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]

What ships with it: 1 file

3.6 KB alongside SKILL.md

reference/

Keep looking

Skills are one crate of 325,949. 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.