Une bonne structure de dossiers répond instantanément à deux questions : « où trouver X ? » et « où mettre Y ? ». La recommandation actuelle (et celle de ce site) est d’organiser par domaine (features), pas par type de fichier, avec deux zones transverses : core et shared.
La structure recommandée
src/app/
├── app.ts · app.config.ts · app.routes.ts
│
├── core/ ← une fois par application : auth, layout, erreurs, interceptors
│ ├── auth/
│ ├── layout/
│ └── error-handling/
│
├── shared/ ← réutilisable, SANS domaine métier
│ ├── ui/ ← composants dumb génériques
│ ├── directives/
│ ├── pipes/
│ └── util/ ← fonctions pures
│
└── features/ ← les domaines métier
├── products/
│ ├── products.routes.ts
│ ├── pages/ ← smart (routées)
│ ├── ui/ ← dumb, propres au domaine
│ ├── data-access/ ← api + store
│ └── models/ ← types, DTO, mappers
├── cart/
└── checkout/La racine de l’application. Ici ne vivent que les fichiers de démarrage et les trois zones : core, shared, features.
Les trois zones, en une phrase chacune
| Zone | La question test | Peut importer depuis |
|---|---|---|
core/ | « Est-ce utilisé une seule fois, au niveau de l’application, ou est-ce de l’infrastructure ? » | shared |
shared/ | « Cela fonctionnerait-il à l’identique copié dans un autre projet ? » | uniquement un autre shared |
features/x/ | « Est-ce lié au domaine X ? » | shared, core (services) |
À l’intérieur d’une feature
À l’intérieur d’une feature, les sous-dossiers suivent les rôles de Smart vs Dumb : pages (smart, routées), ui (dumb), data-access (api + store) et models (types). Ne les créez pas dès le départ — une petite feature se porte très bien à plat :
// Une petite feature ? Pas de sous-dossiers — à plat, jusqu’à ce que ce soit pénible (6 à 8 fichiers environ)
features/settings/
├── settings.routes.ts
├── settings-page.ts
├── settings-page.html
├── settings-store.ts
├── notification-toggle.ts
└── settings.ts ← les typesLazy Loading par feature
// Chaque feature est lazy : un seul point d’entrée (le fichier de routes)
export const routes: Routes = [
{ path: '', loadComponent: () => import('./features/home/home-page') },
{ path: 'products', loadChildren: () => import('./features/products/products.routes') },
{ path: 'cart', loadChildren: () => import('./features/cart/cart.routes') },
{
path: 'admin',
canMatch: [adminGuard],
loadChildren: () => import('./features/admin/admin.routes'),
},
{ path: '**', loadComponent: () => import('./core/layout/not-found') },
];Anti-pattern : des dossiers par type
// ✗ Organisation « par type » — passe mal à l’échelle
src/app/
├── components/ (120 fichiers de tous les domaines mélangés)
├── services/ (40)
├── models/ (60)
└── pipes/
// Pour modifier « checkout », vous ouvrez 4 dossiers et cherchez parmi des centaines de fichiers.
// Impossible de faire un lazy loading propre, impossible d’imposer des frontières entre les domaines.Pour les gros monorepos
// Un gros monorepo (Nx) : les mêmes idées, mais sous forme de bibliothèques avec des tags et des règles vérifiées par le lint
libs/
├── products/
│ ├── feature-list/ type:feature (pages smart)
│ ├── feature-detail/ type:feature
│ ├── ui/ type:ui (dumb)
│ ├── data-access/ type:data-access (api, store)
│ └── util/ type:util
└── shared/
├── ui/ scope:shared, type:ui
└── util/
Règles typiques : feature → ui, data-access, util ✓ ui → util ✓ ui → data-access ✕Règles d’or
- Colocalisation : ce qui change ensemble reste ensemble (le composant, son template, ses styles, son test et ses types).
- À plat tant que ça ne gêne pas : ajoutez un sous-dossier quand il y a trop de fichiers, pas « pour plus tard ».
- Les features ne s’importent pas entre elles. Communiquez via
core(stores globaux),sharedou l’URL. Voir Boundaries. - Chaque feature a un seul point d’entrée : son fichier de routes.
- Le nom du dossier = le nom du domaine, au pluriel ou au singulier — mais de façon cohérente.
Pièges fréquents
sharedcomme « fourre-tout » : tout ce dont vous ne savez pas où le mettre finit là. Voir Shared vs feature pour des règles claires.coreavec des composants réutilisés entre les pages :coresert aux éléments utilisés une seule fois (shell, en-tête), pas à l’UI commune.- Une feature unique « common » ou « general » : c’est un
shareddéguisé, avec des domaines mélangés.