L'idée clé
Un layout est une coquille UI qui reste affichée pendant que les pages enfants changent. Quand l'user navigue de /admin/produits à /admin/clients, le sidebar (dans app/admin/layout.tsx) ne re-monte pas. Seul le page.tsx est échangé.
Ça donne :
- Performances : le sidebar ne se re-télécharge pas, l'état est préservé (scroll, formulaires, etc.)
- UI cohérente : header/footer/nav identiques partout sans copier-coller
- Imbrication : layouts s'enchaînent (root → admin → produits) — chacun englobe ses enfants
Anatomie
// app/admin/layout.tsx
export default function AdminLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex">
<Sidebar />
<main>{children}</main> {/* ← la page de l'URL est rendue ici */}
</div>
);
}La prop magique : children. Next y injecte automatiquement la page.tsx qui correspond à l'URL, ou un layout enfant si l'arborescence en a un.
Le root layout (app/layout.tsx)
C'est le seul layout obligatoire. Il doit contenir les balises <html> et <body> (les autres layouts non, juste des <div> etc.).
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="fr">
<body>{children}</body>
</html>
);
}Tout ce que tu mets là (theme provider, script de thème, fonts) est chargé sur toutes les pages.
Imbrication concrète chez wari
Quand un user visite /admin/produits/42 :
RootLayout (app/layout.tsx)
→ AdminLayout (app/admin/layout.tsx) ← AdminShell, sidebar
→ ProduitsLayout ? (si existe)
→ ProductDetailPage (app/admin/produits/[id]/page.tsx)Chaque layout englobe son enfant. Le sidebar de AdminLayout reste fixe pendant que l'user passe d'un produit à un autre dans /admin/produits/[id].
Pour comparer à ce que tu connais
Si tu as fait du Django : layout.tsx joue le rôle d'un template base.html qu'on hérite via {% extends %}. Sauf que :
- L'imbrication est automatique (par arborescence)
- Le layout est persistant côté client (pas re-rendu à chaque nav)
Piège classique
Ne mets pas d'état (useState) qui doit dépendre de l'URL dans un layout — il ne re-monte pas. Si tu changes de page, le useState garde sa valeur d'avant. Pour de l'état lié à l'URL, utilise useSearchParams() ou mets le state dans page.tsx qui, lui, re-monte.