Quelle est la structure de répertoires standard d’un projet Next.js ?
Une structure de répertoires standard pour un projet Next.js n’est pas un arbre de dossiers unique et correct qui s’applique de manière identique à toutes les équipes. Elle consiste plutôt à suivre les conventions définies pour les fichiers de routage que le framework doit interpréter, tout en plaçant le reste du code selon la nature du projet. Pour un nouveau projet, une approche facile à comprendre consiste généralement à faire de src/app le centre des URL et des points d’entrée des pages, à séparer dans components l’interface utilisée à plusieurs endroits, à placer la logique spécifique à une fonctionnalité dans features lorsque nécessaire, et à conserver les utilitaires partagés dans lib. L’App Router est l’approche de routage principale dans les versions actuelles de Next.js, tandis que le Pages Router reste pris en charge. nextjs.orgnextjs.org
Lorsque vous définissez une structure pour la première fois, l’important n’est pas de créer le plus grand nombre possible de dossiers. Commencez par distinguer les fichiers qui créent des URL, ceux qui gèrent les layouts, les écrans d’erreur et les API, et le code utilisé uniquement au sein d’un écran particulier. Cet article examine une structure maintenable centrée sur l’App Router à partir de ces distinctions.
Pourquoi n’existe-t-il pas de « structure standard unique » dans Next.js ?
Next.js fournit des conventions pour construire des routes à partir de fichiers et de dossiers, mais il n’impose pas précisément les dossiers qui doivent contenir chaque composant et chaque élément de logique métier. Dans l’App Router en particulier, les dossiers représentent des segments d’URL et des fichiers spéciaux tels que page.tsx et route.ts créent de véritables points d’entrée publics. À l’inverse, les fichiers ordinaires placés dans une route ne deviennent pas des routes externes par défaut. nextjs.org
En raison de cette caractéristique, les structures peuvent différer même entre des applications Next.js. Un petit site marketing peut n’avoir besoin que de app et de quelques composants partagés. En revanche, dans un service comportant plusieurs domaines tels que des tableaux de bord, des commandes et des comptes, séparer le code spécifique aux fonctionnalités du code partagé facilite la compréhension de la portée des modifications. Il ne s’agit pas d’une règle absolue imposée par Next.js, mais d’une approche d’organisation du code qu’une équipe choisit en complément des conventions de routage.
Il est donc moins source de confusion de considérer le terme « standard » selon les deux niveaux suivants.
| Catégorie | Nature | Exemples typiques |
|---|---|---|
| Conventions interprétées par Next.js | Les noms et emplacements de fichiers influencent directement le comportement | app/page.tsx, app/layout.tsx, app/api/users/route.ts |
| Conventions définies par le projet | L’équipe définit les noms et les limites selon ses besoins | components, features, lib, hooks, types |
Modifier arbitrairement le premier niveau peut changer le routage ou le comportement d’une interface spéciale. Le second niveau peut être omis ou combiné selon l’échelle et la complexité du domaine. Par exemple, une structure sans features est possible, et il n’est pas nécessaire de sur-segmenter chaque élément de code dans un petit projet.
Pourquoi les nouveaux projets devraient-ils être conçus autour de l’App Router ?
L’App Router est une approche permettant de construire des pages et des layouts à partir du répertoire app. La documentation officielle recommande de passer du Pages Router à l’App Router pour tirer parti des dernières fonctionnalités de React, tandis que le Pages Router lui-même reste pris en charge. Vous devez donc comprendre les conventions de pages lorsque vous maintenez ou étudiez une structure de projet existante, mais il est naturel de considérer l’App Router comme le candidat par défaut lors de la conception d’une nouvelle structure. nextjs.org
L’élément clé de l’App Router est de relier la hiérarchie des URL à la hiérarchie des fichiers. Par exemple, app/dashboard/page.tsx devient le point d’entrée de la page /dashboard, tandis que app/dashboard/layout.tsx, situé au même niveau, peut fournir un layout appliqué aux sous-routes du tableau de bord. Le fichier racine app/layout.tsx est le layout racine qui englobe toutes les routes. nextjs.org
Cette approche convient bien pour conserver le code au niveau de l’écran à proximité de celui-ci. Les onglets, tableaux et interfaces de filtrage nécessaires uniquement à /dashboard peuvent rester sous app/dashboard, tandis que les boutons ou champs de saisie réutilisés sur plusieurs écrans peuvent être déplacés vers un dossier partagé externe. Le critère essentiel n’est pas « quelle technologie a été utilisée pour écrire ce fichier », mais « dans quelle portée est-il réutilisé ? »
Cependant, utiliser l’App Router ne signifie pas que tout le code doit être placé dans app. Vous pouvez traiter app comme une frontière qui rend les URL et les fichiers spéciaux faciles à lire, puis séparer le code partagé ou les implémentations complexes de fonctionnalités dans d’autres dossiers selon les besoins. Cette séparation n’est pas effectuée automatiquement par Next.js ; il s’agit d’une convention structurelle que l’équipe définit et applique de façon cohérente.
Quand utiliser un dossier src et que conserver à la racine ?
src est facultatif. Lorsque vous l’utilisez, vous pouvez regrouper app et le code source de l’application sous src, ce qui sépare visuellement les fichiers de configuration du code exécuté. À l’inverse, public, package.json, next.config.js, tsconfig.json et les fichiers .env.* appartiennent à la racine du projet. nextjs.org
Voici un exemple simple à comprendre lorsque vous utilisez l’App Router avec src.
my-app/
├─ public/
│ ├─ images/
│ └─ fonts/
├─ src/
│ ├─ app/
│ │ ├─ layout.tsx
│ │ ├─ page.tsx
│ │ ├─ globals.css
│ │ ├─ (marketing)/
│ │ │ └─ about/
│ │ │ └─ page.tsx
│ │ ├─ dashboard/
│ │ │ ├─ layout.tsx
│ │ │ ├─ page.tsx
│ │ │ ├─ loading.tsx
│ │ │ ├─ error.tsx
│ │ │ └─ _components/
│ │ └─ api/
│ │ └─ users/
│ │ └─ route.ts
│ ├─ components/
│ ├─ features/
│ ├─ lib/
│ ├─ hooks/
│ └─ types/
├─ .env.local
├─ next.config.js
├─ package.json
└─ tsconfig.json
Dans cet exemple, src constitue uniquement une frontière pour le code de l’application, et non un mécanisme obligatoire qui modifie le comportement. Si un projet existant possède déjà app à la racine, il peut être préférable de suivre systématiquement la convention établie plutôt que de forcer l’ajout de src. En particulier, si des répertoires appelés app ou pages existent à la fois à la racine et dans src, le répertoire racine est prioritaire ; il est donc important de ne pas laisser des structures dupliquées en place pendant une longue période lors d’une migration. nextjs.org
Le critère pratique pour choisir src est simple. Il est utile si vous souhaitez séparer clairement les fichiers de configuration du code produit ou si vous prévoyez une hausse du nombre de fichiers sources. À l’inverse, il n’est pas nécessaire de l’adopter dans un projet d’apprentissage ou dans un très petit projet dont la structure racine est déjà claire.
Quels fichiers du dossier app créent de véritables routes ?
Dans l’App Router, les dossiers représentent des parties d’une URL, ou segments. Cependant, créer uniquement un dossier app/dashboard ne rend pas /dashboard publique. Si ce dossier contient page.tsx, il devient une route qui fournit une interface de page ; s’il contient route.ts, il devient un point de terminaison d’API reposant sur un Route Handler. nextjs.org
Prenons par exemple la disposition suivante.
src/app/
├─ page.tsx
├─ about/
│ └─ page.tsx
├─ dashboard/
│ ├─ page.tsx
│ └─ reports/
│ └─ page.tsx
└─ api/
└─ users/
└─ route.ts
Dans ce cas, page.tsx correspond respectivement à /, /about, /dashboard et /dashboard/reports. api/users/route.ts n’est pas une page d’interface ; il définit un point de terminaison d’API. Dès lors que vous comprenez que page.tsx et route.ts servent de points d’entrée publics d’une route, il devient clair pourquoi d’autres fichiers peuvent se trouver dans le même dossier. nextjs.org
Cette propriété peut être considérée comme de la colocalisation. La colocalisation est une approche d’organisation qui conserve le code associé à proximité. Par exemple, les composants de tableau utilisés uniquement dans dashboard, les fonctions de formatage propres à l’écran et le code de transformation des données de test peuvent être placés près de app/dashboard. Cela ne signifie pas que les dossiers partagés ne doivent jamais être utilisés. Il suffit de déplacer le code réutilisé par d’autres routes vers des dossiers ayant une portée plus large.
Comment séparer les fichiers layout, loading et error ?
layout.tsx gère l’enveloppe d’interface partagée. Le fichier racine app/layout.tsx englobe toutes les routes, tandis qu’un layout.tsx situé dans un dossier enfant s’applique de manière imbriquée à ses sous-routes. Par exemple, si la navigation du tableau de bord est partagée par /dashboard et /dashboard/reports, elle peut se trouver dans app/dashboard/layout.tsx. nextjs.org
page.tsx correspond à l’interface de page affichée à un chemin précis. Si un layout constitue la structure extérieure répétée, une page se rapproche davantage du contenu propre au chemin qui change à l’intérieur. Les séparer évite de répéter la navigation ou les cadres partagés dans chaque page.
L’App Router fournit également des fichiers réservés pour les interfaces propres à certains états. loading.tsx est utilisé pour l’interface de chargement, error.tsx pour l’interface d’erreur et not-found.tsx pour l’interface d’absence de résultat. Contrairement aux fichiers de composants ordinaires, Next.js interprète ces fichiers pour des rôles spécifiques ; ils doivent donc être placés en tenant compte de leur rôle et de leur portée. nextjs.org
Par exemple, si vous souhaitez afficher un écran distinct pendant la préparation des données sous /dashboard, vous pouvez envisager app/dashboard/loading.tsx ; si vous avez besoin d’un écran de gestion des erreurs dans cette portée, vous pouvez envisager app/dashboard/error.tsx. Il n’est pas nécessaire d’ajouter ces fichiers mécaniquement dans chaque dossier. Décider en fonction du besoin réel de chaque route en matière d’interface d’attente, d’erreur ou d’absence de résultat permet de conserver une structure concise.
Comment les routes dynamiques et les routes API sont-elles représentées dans les noms de dossiers ?
Utilisez la notation entre crochets lorsqu’une partie d’une route n’est pas prédéterminée. [slug] désigne un segment dynamique, [...slug] désigne tous les sous-segments suivants et [[...slug]] désigne une forme facultative dans laquelle ces sous-segments peuvent être absents. nextjs.org
Par exemple, une page dont l’identifiant d’article varie peut être structurée comme suit.
src/app/posts/
└─ [slug]/
└─ page.tsx
Dans cette structure, [slug] n’est pas un nom de dossier fixe ; c’est un emplacement qui reçoit la partie variable de l’URL. À l’inverse, lorsqu’une convention unique doit gérer plusieurs niveaux d’une route, envisagez [...slug] ou [[...slug]]. Le choix de la notation dépend de la nécessité qu’au moins un sous-chemin existe et de la nécessité que le cas sans chemin soit également géré par le même écran. nextjs.org
Lors de la création d’API, route.ts est le point d’entrée d’un Route Handler. Vous pouvez donc représenter la structure des URL avec des dossiers tels que app/api/users/route.ts et placer route.ts à la fin. Pour les pages comme pour les API, la lecture de la hiérarchie des dossiers permet de déduire le chemin approximatif. Il est toutefois préférable de séparer de manière appropriée les fichiers d’implémentation dédiés afin que le code d’interface et le code de traitement côté serveur ne se mélangent pas excessivement et ne s’allongent pas dans la même zone. nextjs.org
Pourquoi les groupes de routes et les dossiers privés sont-ils nécessaires ?
Un dossier entouré de parenthèses, tel que (marketing), est un groupe de routes. Il s’agit d’un regroupement logique qui n’est pas inclus dans l’URL. Vous pouvez par exemple regrouper des pages orientées marketing telles que les informations sur l’entreprise et les tarifs, tout en gérant les écrans d’utilisation du produit dans une structure distincte. app/(marketing)/about/page.tsx devient la route /about sans le nom du groupe. nextjs.org
Les groupes de routes sont utiles lorsque vous souhaitez faire apparaître des limites de layout ou de responsabilité du code sans modifier l’URL. Cependant, comme le nom du groupe n’apparaît pas dans l’URL, un conflit survient si des groupes distincts créent au final la même URL. De plus, une configuration qui navigue entre plusieurs layouts racines peut provoquer un chargement complet de la page ; il ne faut donc pas diviser les layouts racines à la légère uniquement pour rendre les dossiers plus ordonnés. nextjs.org
Les dossiers qui commencent par un trait de soulignement, tels que _components et _lib, sont des dossiers privés et sont exclus du routage. Dans l’App Router, les fichiers ordinaires ne deviennent pas des routes par défaut ; un trait de soulignement n’est donc pas strictement nécessaire. Il peut néanmoins être utile lorsque vous voulez signaler visiblement la limite entre les fichiers spéciaux de routage et l’implémentation interne, ou éviter toute confusion avec les noms de fichiers réservés. nextjs.org
Par exemple, app/dashboard/_components/summary-card.tsx indique qu’il s’agit d’une interface spécifique au tableau de bord. Toutefois, si ce composant commence à être utilisé de manière répétée dans d’autres fonctionnalités, il est plus naturel d’envisager de le déplacer hors du dossier commençant par un trait de soulignement vers components partagé ou vers une limite de fonctionnalité appropriée. Gardez à l’esprit qu’un préfixe de dossier n’est pas un mécanisme de contrôle d’accès ; c’est une notation qui communique le rôle du code.
Comment distinguer components, features, lib, hooks et types ?
Ces dossiers sont des choix d’organisation facultatifs, et non des conventions réservées de l’App Router. Les limites de responsabilités convenues par l’équipe importent donc davantage que les noms eux-mêmes. Les distinctions suivantes constituent un point de départ courant.
| Dossier | Code généralement placé ici | Critère de placement |
|---|---|---|
components | Interface réutilisée sur plusieurs écrans | N’est-elle pas liée à une URL ou à un domaine spécifique ? |
features | Implémentation au niveau d’une fonctionnalité ou d’un domaine | Existe-t-il un concept métier clair, tel que les comptes, les commandes ou les tableaux de bord ? |
lib | Utilitaires et clients partagés | S’agit-il d’un outil partagé plutôt que d’une interface ? |
hooks | Hooks réutilisables | Plusieurs composants partagent-ils le même état ou comportement ? |
types | Types partagés | Plusieurs zones font-elles référence aux mêmes définitions de types ? |
components peut contenir non seulement des boutons génériques, mais aussi des interfaces composites partagées par plusieurs fonctionnalités. Cependant, transformer dès le départ chaque élément d’interface en composant partagé globalement peut abstraire des implémentations dont un seul écran a réellement besoin. Une autre approche consiste à conserver le code près de la route au départ, puis à le déplacer lorsqu’il est utilisé de façon stable à deux endroits ou plus et possède une interface partagée claire.
features est particulièrement facile à lire lorsqu’une structure centrée sur les domaines est nécessaire. Par exemple, si les commandes et les comptes ont chacun des écrans, une interface et du code de traitement de données indépendants, vous pouvez les regrouper sous features/orders et features/account. À l’inverse, créer trop de dossiers de fonctionnalités dans un site simple peut obliger les personnes à parcourir de nombreux dossiers uniquement pour trouver des fichiers. Il est préférable de ne les introduire que lorsque les limites des fonctionnalités correspondent à de véritables concepts du produit.
lib est un emplacement possible pour le code d’infrastructure non lié à l’interface, comme les utilitaires partagés ou les clients serveur. Cependant, si tous les fichiers de fonctions s’accumulent dans lib, ce dossier peut devenir une vaste zone de stockage difficile à comprendre. Une règle pratique consiste à conserver les outils utilisés par une seule fonctionnalité près de cette fonctionnalité ou de cette route, et à ne déplacer vers lib que le code partagé à plusieurs endroits.
Pourquoi public et les fichiers de variables d’environnement appartiennent-ils à la racine du projet ?
public est le dossier situé à la racine du projet pour les fichiers statiques. Les fichiers qui y sont placés sont servis depuis le chemin racine ; par exemple, public/profile.png est référencé comme /profile.png. Cela fournit un emplacement unique pour les fichiers servis statiquement, tels que les images et les polices. nextjs.org
Même lorsque vous utilisez src, vous ne devez pas considérer la structure comme un déplacement de public vers src/public. public reste à la racine du projet, et package.json, next.config.js, tsconfig.json et .env.* sont également gérés depuis la racine. En particulier, les fichiers locaux de variables d’environnement tels que .env.local pouvant contenir des secrets, il est important d’établir une règle opérationnelle visant à ne pas les inclure dans le contrôle de version. nextjs.org
Distinguer les rôles des ressources statiques et du code source de l’application facilite l’interprétation des chemins. Les fichiers de src/app sont du code qui construit les écrans ou le routage, tandis que les fichiers de public sont des ressources statiques référencées par URL. Même lorsqu’une même image est utilisée sur un écran, il faut comprendre différemment sa méthode de distribution et son chemin de référence selon l’emplacement du fichier.
En quoi cette structure diffère-t-elle de celle du Pages Router ?
Le Pages Router fonctionne en traitant les fichiers du répertoire pages comme des routes. Par exemple, pages/index.tsx correspond à / et pages/about.tsx correspond à /about. Les conventions de fichiers réservés attribuent également des rôles spéciaux à _app, _document, 404, 500 et d’autres fichiers. nextjs.org
Il est facile de commettre des erreurs si vous considérez l’App Router et le Pages Router comme des approches qui ne diffèrent que par le nom du dossier. Dans l’App Router, les fichiers spéciaux tels que page.tsx, layout.tsx et route.ts sous les segments de dossiers répartissent les responsabilités, tandis que les fichiers ordinaires ne sont pas des routes par défaut. Dans le Pages Router, les fichiers situés dans pages sont plus directement liés aux routes. nextjs.orgnextjs.org
Lorsque vous travaillez sur un projet Pages Router existant, vous devez respecter ses conventions actuelles basées sur pages. À l’inverse, lorsque vous démarrez un nouveau projet, baser la structure sur un exemple centré sur l’App Router et ajouter des dossiers d’organisation uniquement lorsqu’ils sont réellement nécessaires réduit la surcharge. Il est important de ne pas mélanger les conventions des deux routeurs dans une même conception de répertoires.
Quels critères utiliser pour choisir une structure dans un projet réel ?
Commencez par la structure des URL. Répertoriez les principaux chemins auxquels les utilisateurs accéderont, puis déterminez quels layouts partagés utilise chaque chemin. Représentez le résultat par les dossiers dans app et le placement de page.tsx et layout.tsx. Utilisez les groupes de routes lorsqu’il existe une raison claire de séparer des zones d’écran ou des layouts sans modifier les URL. nextjs.orgnextjs.org
Ensuite, évaluez la portée de la réutilisation. Conservez près de la route le code utilisé par une seule route. L’interface utilisée par plusieurs routes peut être déplacée vers une portée plus large, telle que components, et les utilitaires utilisés par plusieurs fonctionnalités peuvent être déplacés vers lib. Plutôt que de généraliser dès le début, séparer le code lorsque de véritables motifs de réutilisation et de modification émergent aide à réduire les abstractions inutiles.
Troisièmement, examinez l’indépendance des fonctionnalités. Si des fonctionnalités avec des domaines de responsabilité et une terminologie clairs — telles que les comptes, l’administration ou les commandes — prennent de l’ampleur, des limites de domaine comme features peuvent être utiles. En revanche, lorsqu’il y a peu d’écrans et que les distinctions entre fonctionnalités sont faibles, la colocalisation dans app accompagnée d’un petit nombre de dossiers partagés peut suffire.
Quatrièmement, vérifiez le coût de recherche pour l’équipe. Lorsqu’un nouveau membre de l’équipe cherche le code d’une URL particulière, il doit pouvoir suivre le chemin dans app et trouver la page ainsi que l’implémentation dédiée. Les noms et emplacements des boutons ou utilitaires partagés doivent également être prévisibles. Une bonne structure repose davantage sur cette prévisibilité que sur des noms de dossiers à la mode.
Quelles idées fausses et quels pièges structurels faut-il éviter ?
La première idée fausse consiste à croire que « créer un dossier crée immédiatement une URL ». Dans l’App Router, un dossier représente un segment, mais il doit contenir page.tsx ou route.ts pour devenir une page publique ou un point de terminaison d’API. Cette règle permet aux fichiers internes associés de cohabiter dans le dossier de la route. nextjs.org
La deuxième idée fausse consiste à croire que « sans dossier commençant par un trait de soulignement, tous les fichiers internes sont exposés ». Les fichiers ordinaires de l’App Router ne sont pas des routes par défaut. Un nom tel que _components n’est pas une fonctionnalité de sécurité obligatoire ; c’est un outil d’organisation qui indique une implémentation interne et exclut le dossier du routage. nextjs.org
La troisième idée fausse consiste à croire que « les noms des groupes de routes sont aussi inclus dans les URL ». Le nom entre parenthèses dans (marketing) est exclu de l’URL. En raison de cette commodité, vous devez vous assurer que différents groupes ne créent pas la même URL finale. Si vous divisez plusieurs layouts racines, la possibilité d’un chargement complet de la page lors de la navigation entre les groupes est un autre élément à prendre en compte avant de concevoir la structure. nextjs.org
Enfin, évitez l’erreur consistant à conserver des répertoires app ou pages correspondants à la fois à la racine et dans src lors de l’introduction de src. Puisque la racine est prioritaire dans ce cas, il peut sembler que le code source attendu ne s’exécute pas. Modifier une structure ne consiste pas simplement à ajouter tous les dossiers d’un seul coup ; procédez en vérifiant quel répertoire sert réellement de base au routage. nextjs.org
Conclusion : le standard concerne les limites de rôle, pas une liste de dossiers
Le point de départ d’une structure de projet Next.js est constitué des conventions de routage définies par app et les fichiers spéciaux. Dans un nouveau projet App Router, vous pouvez faire de src/app le centre des URL, des pages et des layouts ; conserver public comme dossier de ressources statiques à la racine ; et choisir components, features, lib, hooks et types selon la portée réelle de réutilisation du code et la complexité du domaine. nextjs.orgnextjs.org
En définitive, une bonne structure n’est pas celle qui comporte le plus de dossiers. C’est celle où les membres de l’équipe peuvent facilement prévoir l’emplacement d’un écran associé à une route particulière, du code dédié à cet écran et du code partagé à plusieurs endroits. Suivez précisément les conventions de fichiers de Next.js, puis adaptez progressivement l’approche d’organisation qui les complète à mesure que le rythme de croissance du projet et les schémas de modification évoluent.