Que fait Conventions de codage pour applications web Next.js ?
Appliquez ces conventions au travail demandé. Les instructions explicites de l'utilisateur prévalent sur toute règle ou valeur par défaut de cette compétence. Préservez les exigences et intégrations existantes du produit ; l'application de cette compétence n'autorise pas une migration non liée ou l'installation de toutes les dépendances optionnelles.
Avant l'implémentation, inspectez les instructions du projet, les versions installées et les conventions existantes. Lisez la documentation Next.js installée pertinente lorsqu'elle est disponible, en particulier node_modules/next/dist/docs/ ; sinon, consultez la documentation officielle appropriée à la version. Ne supposez pas d'API du framework d'une autre version.
Valeurs par défaut et fonctionnalités conditionnelles
| Domaine | Valeur par défaut | Règle de remplacement ou d'activation |
|---|---|---|
| Framework | Next.js App Router et TypeScript strict | Respectez les versions choisies du projet et les choix explicites de l'utilisateur. |
| Composants | Base UI avec Tailwind CSS, enveloppé dans des composants locaux | Supportez Radix, shadcn/ui, ou une autre bibliothèque demandée. Préservez la même organisation des composants. |
| Variantes | class-variance-authority et un assistant de fusion de classes compatible Tailwind | Réutilisez un équivalent établi. |
| Transport API | Axios derrière les abstractions APIClient et ServerAPIClient personnalisées | Un client personnalisé existant, fetch, ou un autre transport demandé est valide. Gardez les contrats indépendants du transport. |
| Validation | Schémas Zod avec types TypeScript inférés | Requis par défaut aux frontières de données externes ; ne changez que sur instruction explicite de l'utilisateur. |
| Données client distantes | TanStack Query avec un wrapper de requête partagé | Réutilisez un équivalent demandé. |
| État interactif partagé | Zustand | Utilisez l'état React local pour l'état appartenant à un seul composant. Respectez les alternatives explicites. |
| Authentification | Flux OTP par e-mail neutre vis-à-vis du fournisseur lorsque l'authentification est demandée | Suivez le fournisseur sélectionné et les méthodes de connexion demandées ; ne choisissez ni ne provisionnez un fournisseur implicitement. |
| Synchronisation cross-tab | Activée pour les événements de mutation partagés pertinents | Optionnelle et supprimable sur demande. Ajoutez des événements uniquement là où la cohérence cross-tab est utile. |
| Journalisation et traçage | Suivez un système de journalisation existant | S'il n'en existe aucun, n'introduisez pas, ne proposez pas et ne mentionnez pas l'infrastructure de journalisation/traçage sauf si demandé. |
| Données simulées et simulation | Désactivé | Ajoutez uniquement lorsque l'utilisateur demande des données simulées ou une simulation de développement/test. |
| Outillage de développement | Modules séparés réservés au développement | Ajoutez des outils uniquement lorsqu'ils sont pertinents ou demandés ; aucun outil ou superposition prescrit. |
Choisissez les valeurs par défaut lorsqu'une décision est nécessaire ; ne demandez pas chaque valeur par défaut. N'introduisez pas l'authentification, les magasins d'état, la synchronisation ou l'infrastructure dans des fonctionnalités qui n'en ont pas besoin.
Structure de dossiers requise
Utilisez cette organisation pour le nouveau code. Créez les répertoires à mesure que leurs responsabilités deviennent nécessaires plutôt que de remplir le projet d'espaces réservés vides. Dans un projet existant, évitez de déplacer des fichiers non liés sans une demande de migration.
src/
├── app/ Routes, layouts, points de terminaison API
├── components/
│ ├── ui/ Primitives d'interface partagées
│ ├── standalone/ Composants de domaine réutilisables
│ ├── pages/<page>/ Composants spécifiques à une page
│ └── <feature>/ Sous-systèmes de fonctionnalité
├── controllers/
│ ├── client/<domain>/ Appels API navigateur
│ └── server/<domain>/ Appels API côté serveur
├── providers/ Fournisseurs de contexte React
├── stores/ Magasins Zustand
├── hooks/ Hooks React partagés
├── helpers/ Authentification/accès et autres workflows
├── lib/ Clients API, cache, journalisation, synchronisation
├── db/ Clients de base de données/Redis
├── utils/ Utilitaires et constantes
└── assets/icons/ SVG sources
Les noms de dossiers pour l'infrastructure décrivent le placement, pas les services requis. Aucune base de données, Redis, cache ou pile de journalisation n'est implicite par cette arborescence.
- Organisez le code de domaine sous ces dossiers de responsabilité ; évitez de les remplacer par une architecture de niveau supérieur différente.
- Utilisez des fichiers de composants en PascalCase, des hooks
useSomething.ts(.tsxuniquement lorsque JSX est présent) et des fichiers d'opérations en camelCase commeupdateName.ts. - Utilisez des noms de répertoires de domaine et de fonctionnalité descriptifs. Gardez les noms de fichiers du framework tels que
page.tsx,layout.tsxetroute.tsinchangés. - Utilisez
index.tspour assembler les espaces de noms des contrôleurs etindex.tsxcomme point d'entrée d'un composant multi-fichiers. - Utilisez
@/pour les imports enracinés danssrc; utilisez des imports relatifs pour les frères internes proches. Utilisezimport typepour les dépendances de type uniquement. - Évitez les copies temporaires, les dossiers de sauvegarde, les implémentations en double et les fichiers fourre-tout avec des responsabilités floues.
Routes et composition des composants
Gardez app/ concentré sur le routage, la composition des layouts, la gestion des requêtes et le chargement initial des données. Utilisez des groupes de routes tels que (auth), (no-auth) et (settings) lorsque leurs limites de layout sont utiles. Un nom de groupe de routes seul ne fournit aucune protection d'accès.
Gardez les pages légères : chargez les données initiales, résolvez les décisions au niveau de la page et rendez les composants de components/pages/<page>/ ou du répertoire de fonctionnalité pertinent. Les composants serveur sont la valeur par défaut ; introduisez des limites client pour l'état du navigateur, les événements et les API.
Placement des composants :
components/ui/: primitives indépendantes du domaine et wrappers autour de la bibliothèque d'interface utilisateur choisie.components/standalone/: contrôles de domaine réutilisables utilisés dans toutes les pages ou fonctionnalités.components/pages/<page>/: composants spécifiques à une page.components/<feature>/: sous-systèmes plus volumineux partagés entre les routes, tels que la navigation ou une interface de commande.
Divisez les composants complexes en fichiers ciblés. C'est une convention fondamentale. Un composant conséquent doit avoir un répertoire avec un point d'entrée de composition index.tsx et des composants de support nommés, des onglets, des lignes, des hooks et des types si nécessaire. N'accumulez pas plusieurs sous-composants conséquents et effets non liés dans un seul fichier. Divisez par responsabilité plutôt que par une limite de lignes arbitraire ; évitez de fragmenter le balisage trivial.
Gardez les mécanismes d'interaction dans des hooks locaux à la fonctionnalité ou des composants de gestionnaire ciblés : navigation au clavier, sélection, abonnements aux événements, comportement de glisser et coordination des vues. Déplacez un hook vers src/hooks/ lorsqu'il est partagé. Gardez les raccourcis de saisie limités pour qu'ils n'interfèrent pas avec les contrôles éditables ; nettoyez les écouteurs lors du démontage.
Enveloppez la bibliothèque primitive localement et exposez les parties composables telles que Dialog, DialogTrigger, DialogContent et DialogTitle. Préservez la sémantique accessible, le comportement de focus et l'API de composition de la bibliothèque réelle. Ne copiez pas les props spécifiques à Radix dans les wrappers Base UI sans vérifier la compatibilité.
Tapez les props du wrapper à partir de la primitive sous-jacente ou de l'élément natif et n'ajoutez que les props personnalisées nécessaires. Utilisez des variantes typées pour la taille, l'emphase et l'état. Fusionnez les classes du consommateur de manière prévisible et préservez la prise en charge de ref selon les API React/bibliothèque installées.
Utilisez des variables CSS sémantiques connectées aux utilitaires Tailwind. Gardez les valeurs de thème centralisées et les variantes de composants locales. Cette compétence prescrit l'organisation du style, pas un thème visuel, une échelle d'espacement ou une conception de produit.
Composez les fournisseurs de contexte pertinents dans app/providers.tsx ; gardez leurs implémentations dans providers/. Gardez les fournisseurs réservés au navigateur derrière une limite client explicite et évitez de mélanger l'initialisation des outils de développement avec les responsabilités normales du fournisseur.
Contrôleurs et clients HTTP
Utilisez une opération par fichier sous controllers/client/<domain>/ ou controllers/server/<domain>/. Assemblez des objets d'espace de noms imbriqués via des fichiers index.ts, par exemple :
ClientController.org.members.list()
ClientController.profile.updateName(input)
ServerController.org.info()
Les contrôleurs fournissent des opérations typées et la construction de points de terminaison. Les composants doivent appeler les contrôleurs plutôt que d'assembler des URL ou la configuration de transport. Les contrôleurs restent légers ; mettez la logique de workflow réutilisable dans helpers/ et l'infrastructure dans lib/.
Gardez les clients HTTP navigateur et serveur séparés :
lib/APIClient.ts: transport sûr pour le navigateur, sérialisation de requête/corps, gestion des réponses et support d'annulation.lib/ServerAPIClient.ts: transport uniquement serveur, URL de base de confiance et contexte d'identification/session limité à la requête.- Gardez les secrets serveur et les dépendances uniquement serveur en dehors des imports navigateur. Ne conservez jamais les informations d'identification par utilisateur dans un singleton mutable partagé.
- Lorsque les contrôleurs serveur appellent des points de terminaison d'application, transmettez uniquement les informations d'identification requises aux destinations de confiance. Ne faites pas confiance à un en-tête d'identité fourni par l'appelant comme authentification.
- Normalisez les erreurs de transport, les réponses non 2xx et les erreurs d'application de manière cohérente. Préservez les informations de statut/code significatives. Ne transformez pas les requêtes ayant échoué en données vides réussies.
- Supportez
AbortSignalvia les couches de contrôleur et de transport. L'annulation ne doit pas déclencher de notifications d'erreur ou de nouvelles tentatives.
Schémas d'API, contrats et gestionnaires
Les schémas Zod sont la source de vérité par défaut pour les entrées. Définissez des schémas spécifiques pour le corps, la requête et les paramètres de chemin, y compris les formats, les valeurs autorisées, les limites de longueur/plage, l'optionalité et la coercition délibérée. Validez avant d'utiliser des valeurs externes. Évitez z.any(), les enregistrements larges, les casts et les assertions non nulles comme substituts aux contrats. Traitez les clés inconnues délibérément pour chaque frontière.
Colocalisez les schémas spécifiques à la méthode et les types de requête/requête/réponse exportés avec la route par défaut. Inférez les types de requête avec z.infer ; définissez des contrats de réponse explicites et utilisez les schémas de réponse là où une validation à l'exécution est nécessaire. Réutilisez ces types dans les contrôleurs et les composants au lieu de les redéclarer. Si les contrats à l'exécution doivent être partagés avec le code navigateur, ou si les contraintes d'exportation du module de route l'exigent, utilisez un schema.ts ou types.ts adjacent et des imports de type uniquement afin que l'implémentation serveur ne soit jamais extraite dans le bundle client.
Utilisez une enveloppe de réponse discriminée cohérente :
type APIResponse<T> =
| { success: true; data: T; error: null }
| {
success: false;
data: null;
error: { code: string; message: string; details?: unknown };
};
Réduisez details aux types de validation/erreur explicites et sûrs là où ils sont utilisés. Gardez les erreurs et les statuts HTTP cohérents sur toutes les branches. N'exposez pas les exceptions internes ou les traces de pile dans les réponses de production.
Organisez les routes multi-méthodes en sections de schémas/types spécifiques à la méthode et en fonctions telles que get_request et post_request. Exportez les gestionnaires via withAPIRequestHandler() dans lib/api/APIRequestHandler.ts.
Maintenez lib/api/error/ avec une classe APIError et un catalogue typé et organisé par domaine de codes d'erreur, messages et statuts. Le gestionnaire partagé mappe les échecs de validation, les erreurs de domaine et les échecs inattendus au même contrat de réponse. Intégrez la journalisation uniquement dans la condition de journalisation ci-dessus.
Extrayez la recherche d'organisation répétée, la résolution d'identité, l'analyse de requête et les workflows d'accès dans helpers/. Gardez les petites transformations pures et les constantes dans utils/. Divisez les intégrations plus volumineuses en fichiers de workflow ciblés ; la route doit orchestrer ces étapes plutôt que de contenir l'intégration entière.
Récupération de données et état
Chargez les données initiales de la page via des contrôleurs serveur lorsque cela est approprié et transmettez des props typées et sérialisables dans les composants interactifs. Utilisez Promise.all pour les requêtes indépendantes ; préservez l'ordre pour les opérations dépendantes.
Utilisez TanStack Query pour les données client distantes. Fournissez des valeurs par défaut partagées et un wrapper useApiQuery qui désenveloppe l'enveloppe de l'API, propage les erreurs et transmet le signal d'annulation de la requête. Les clés de requête doivent inclure les entrées d'identité/ressource/filtre pertinentes. Limitez les clients de requête côté serveur par requête ; invalidez ou mettez à jour les entrées de requête affectées après les mutations.
Utilisez des magasins Zustand spécifiques à la fonctionnalité pour l'état interactif partagé tel que la sélection, les filtres, les panneaux et l'état de commande. Abonnez-vous à des tranches individuelles ou utilisez une sélection superficielle pour les valeurs groupées. Gardez les setters/actions explicites et réinitialisez l'état lorsque sa ressource propriétaire change. Évitez de maintenir une deuxième copie indépendante des données distantes dans Zustand lorsque Query les possède déjà.
Utilisez l'état local pour les entrées appartenant au composant, l'état sale, les messages de validation et l'état de sauvegarde. Empêchez les soumissions en double et préservez la saisie de l'utilisateur en cas d'échec. Il n'y a pas de bibliothèque de formulaires requise.
Annulez les requêtes de recherche/filtre obsolètes et abandonnez lors du nettoyage. Protégez-vous contre les achèvements périmés mettant à jour une nouvelle ressource. Ne classez que l'annulation réelle comme annulation ; n'avalez pas silencieusement les échecs réseau.
Pour les grandes listes, appliquez la virtualisation et la pagination lorsque cela est utile, avec les états de chargement, vide, erreur, nouvelle tentative et fin de liste. Réinitialisez la pagination et la sélection de manière appropriée lorsque la ressource/le filtre change. Gardez la navigation au clavier cohérente avec les lignes virtualisées.
Synchronisation cross-tab optionnelle
Activez la synchronisation pour les mutations partagées pertinentes par défaut, sauf si l'utilisateur la désactive. Ne l'ajoutez pas à un état isolé sans besoin de synchronisation.
Utilisez un registre d'événements typé dans lib/local-sync/, des schémas d'événements de domaine sous events/ et un hook partagé hooks/useLocalSync.ts. Inférez les types de payload de Zod et validez à la fois les messages sortants et entrants.
Une implémentation d'événement de stockage doit notifier les autres onglets et les écouteurs du même onglet, utiliser des payloads d'événement transitoires plutôt que l'état d'application persistant, et nettoyer les écouteurs. Un transport équivalent existant est acceptable. Empêchez les boucles d'écho et la gestion en double ; ne synchronisez pas les secrets. Les événements doivent mettre à jour ou invalider le propriétaire d'état canonique. Les événements cross-tab sont une coordination d'interface utilisateur, pas une autorisation ou une livraison garantie.
Authentification et autorisation
Appliquez cette section lorsque l'authentification est dans le périmètre. Gardez l'implémentation neutre vis-à-vis du fournisseur et adaptez-vous au service sélectionné ou au backend personnalisé.
- Pour l'authentification OTP par e-mail, séparez les étapes d'envoi de code, de vérification de code et de déconnexion. Utilisez des contrôleurs pour les appels API d'authentification ; gérez les jetons dans un magasin Zustand d'authentification avec des fonctions de cycle de vie de session (initialisation, actualisation, déconnexion). Le store de session ne doit pas exporter de valeurs de jeton vers des composants ou des hooks ; exposez des dérivées calculées comme
isAuthenticated,user,sessionStatus. - Utilisez le middleware Next.js pour l'inspection de session initiale. Utilisez des vérifications de session au niveau du composant pendant l'interaction pour protéger les actions. Acceptez un flux où la vérification de session se produit sur le serveur avant le rendu si nécessaire.
- Associez l'état d'authentification à un hook Composable Auth qui fournit
signIn,signOutetuser. Laissez le magasin d'authentification gérer les détails du jeton ; les composants ne doivent pas connaître les jetons, les actualisations ou les en-têtes d'autorisation. - L'infrastructure d'authentification est un prérequis ; ne l'omettez pas dans les fonctionnalités authentifiées. Cependant, n'introduisez pas un fournisseur d'authentification implicite pour des fonctionnalités qui n'en ont pas besoin.
Protection de routage
- Utilisez des groupes de routes (
(auth),(no-auth)) pour les mises en page conditionnelles et le routage, sans vous fier uniquement aux noms de groupes pour la sécurité. - Utilisez le middleware Next.js pour les redirections d'accès initiales. Utilisez les vérifications d'authentification au niveau du composant pour sécuriser le contenu et les actions interactives. Les clés API appartenant à l'utilisateur doivent également être validées comme des sessions, mais les jetons de service et les clés API partagées doivent être validés dans les contrôleurs serveur API uniquement.
Assistant de fusion de classes
Créez un utils/cn.ts exportant une fonction cn qui fusionne les noms de classe et résout les conflits Tailwind. Utilisez clsx ou twMerge. N'exportez pas depuis un wrapper de composant ; importez directement depuis l'utilitaire. Gérez les tableaux, les objets, les conditions et les chaînes vides. Préservez la prise en charge de ClassValue.
Rendu côté serveur et génération de site statique
- Préférez les composants serveur par défaut. Déplacez les composants vers l'interactivité client uniquement lorsque le modèle de rendu du serveur ne peut pas répondre à l'exigence (par exemple, gestionnaires d'événements, état du navigateur, API de navigateur).
- Utilisez
'use client'au niveau du fichier ; évitez les directives dans les fichiers en amont qui enveloppent par inadvertance les composants descendants dans des limites client. - Utilisez les métadonnées exportées
generateMetadatapour le référencement et l'état pré-rendu plutôt que des hooks de navigateur ou des crochets de route. UtilisezgenerateStaticParamspour les routes qui bénéficient de la génération au moment de la construction. - Gardez les modules uniquement serveur derrière des barrières d'importation ; utilisez
server-onlyen cas de doute. - Utilisez des composants de suspense en streaming avec des valeurs de délai d'attente de repli. Utilisez des limites de suspense spécifiques à la fonctionnalité pour le chargement parallèle et les pannes isolées.
Tests
- Écrivez des tests d'intégration pour les API routes avec des entrées simulées de l'utilisateur et du système, en validant la structure de réponse, les codes d'erreur et les transformations de données.
- Écrivez des tests unitaires pour les utilitaires purs, les schémas de validation Zod, les transformations de données et les workflows d'assistance.
- Préférez tester la logique côté serveur plus profondément que la composition de l'interface utilisateur. Testez la logique métier réelle plutôt que l'état du magasin simulé.
- Utilisez
vi.fn()etvi.spyOn()pour les dépendances et les effets secondaires. Gardez les simulations d'API légères. Évitez de vous moquer des internes des bibliothèques. N'incluez pas les tests dans l'artefact de construction. - Utilisez les tests de composants pour les états interactifs complexes, les validations de formulaires et les composants de rétroaction. Évitez les tests d'instantanés pour la composition markup. N'écrivez pas de tests pour des composants de base comme des wrappers. Testez l'affichage conditionnel, l'affichage des erreurs et la gestion des actions utilisateur.
- Utilisez
it.eachettest.concurrentpour les tests paramétrés et la parallélisation. Appliquez une configuration de test partagée avant d'utiliser une réinitialisation ou une simulation. Utilisezdescribe.eachpour les tests de contexte ; évitez les blocsdescribeprofondément imbriqués. - Utilisez
*.test.tsou*.spec.tset co-localisez avec du code de composant ou utilitaire plutôt qu'un répertoire__tests__/plat. Pour les tests d'intégration de route, utiliseztests/api/<route-path>/. Gardez les helpers de test danstests/helpers/outests/utils/.
Messages de commit
Utilisez des messages de commit conventionnels. Commencez par un type tel que feat, fix, refactor, chore, docs, test ou style, suivi de deux points et d'un résumé en anglais. Utilisez le présent impératif. Pas de points à la fin. Un corps facultatif avec des changements détaillés suit une ligne vide. Pour les modifications de plusieurs packages, utilisez scope(composant): message. Utilisez un retour à la ligne automatique à 72 caractères ou moins si un corps est présent.
Scripts de construction et de développement (package.json)
Les scripts package.json en place sont prioritaires sur toute suggestion de cette compétence. Si des scripts manquent, proposez des installations de packages, des commandes d'initialisation et des scripts de construction alignés sur la pile du projet. N'écrasez pas les scripts définis par l'utilisateur sans autorisation. Utilisez des scripts dédiés (par exemple, lint:types, test:e2e) plutôt que de surcharger un seul script.
Erreurs et journalisation sous condition
Ne créez pas de nouvelles classes d'erreur, de codes d'erreur, de gestionnaires d'erreur ou de cadres de journalisation sauf s'ils sont explicitement demandés.
Lorsque la journalisation est en vigueur (elle a été demandée ou existait déjà), le gestionnaire partagé de lib/api/APIRequestHandler.ts doit enregistrer les demandes, les réponses, les avertissements, les exceptions et les informations de performance. Utilisez un journaliseur structuré qui supporte les contextes et les niveaux. Les messages doivent inclure l'ID de demande, la route, le code d'état et la durée.
Lorsque la journalisation n'est pas demandée, les erreurs doivent être envoyées comme code d'état et chaîne de message uniquement. Aucune information de journal, de trace de pile ou de performance ne doit être produite.
Ne créez pas de codes d'erreur jusqu'à ce que le premier code soit utilisé. Ne créez pas de classes d'erreur jusqu'à ce que la première classe d'erreur personnalisée soit nécessaire. Commencez par l'erre la norme Error lorsque cela est suffisant.
Instructions finales
- Lisez toujours la configuration du projet, les fichiers pertinents et les dépendances pour comprendre les conventions de codage en cours. Suivez le langage, le style et la structure existants. Ne modifiez pas les fichiers de configuration majeurs sans instruction explicite.
- Demandez des éclaircissements lorsque l'intention de l'utilisateur n'est pas claire, mais ne posez pas de questions sur les valeurs par défaut de cette compétence.
- Ne détruisez pas le travail existant ; ne remplacez pas l'intégration de la base de données, le fournisseur d'authentification, la mise en cache, le transport HTTP, le système de conception, la configuration de déploiement, l'hébergement, la base de code, les dépendances ou la configuration ESLint/Prettier sans un objectif de refactorisation explicite.
- Effectuez des changements de code lorsque la direction est claire. Restez concentré sur les fonctionnalités demandées.
- Validez la structure de dossiers et les exports avant de générer de nouveaux fichiers. Lisez
package.jsonpour les scripts avant de suggérer des changements. - Lorsque vous ajoutez un package, installez-le avant d'importer, puis importez. Lisez la documentation si nécessaire.
- N'omettez pas les limites de suspense, les états de chargement ou la gestion des erreurs dans les composants interactifs ; envoyez des valeurs de secours et des messages d'erreur significatifs.
- Évitez les composants vides, les fonctions vides et les gestionnaires de route vides. Implémentez le comportement demandé ou un état de secours approprié.
- N'utilisez pas
anydans les schémas de validation, les types de paramètres de requête, les typesparamsde page Next.js, ou les types d'événements de saisie. Utilisez le typage approprié partout. Préférezunknownavec une validation de type àany. - N'utilisez pas
aspour contourner les contrats de type ; tapez les données correctement ou utilisez la validation Zod avec l'inférence de type. - Aucune interface pour les props des composants ; utilisez
type.