# Nested Layout Skeleton Visual map of how pages nest inside each other in the Next.js App Router. Each level persists across navigation — children swap, parents stay. ## Mental model ``` ┌─────────────────────────────────────────────────────────────────┐ │ app/layout.tsx (root) │ │ html / body / fonts / Providers / Toast / CookieBanner │ │ ───────────────────────────────────────────────────────── │ │ No global header. Each route group renders its own chrome. │ └─────────────────────────────────────────────────────────────────┘ │ ┌─────────────────────────┼─────────────────────────────┐ │ │ │ ▼ ▼ ▼ Public routes Storefronts Admin (/, /pricing, (/tuxedo/*, (/admin/*) /contact, /login) /indian-river-direct/*) ``` ## Full tree ``` src/app/ ├── layout.tsx ────────────────────────────────────────────────── [ROOT] │ • , │ • next/font: Fraunces (display), Manrope (sans), Fragment_Mono │ • (theme, posthog, sentry, etc.) │ • │ • │ ─── pages render INSIDE ─── │ │ │ ├── error.tsx [ROOT-LEVEL] Atelier error page │ ├── loading.tsx [ROOT-LEVEL] "Preparing your harvest" │ ├── not-found.tsx [ROOT-LEVEL] "Off the beaten path" │ │ │ ├── page.tsx / LandingPageClient │ │ └── renders its own SiteHeader + Hero + SiteFooter │ │ │ ├── login/ │ │ ├── page.tsx /login LoginClient (standalone) │ │ └── loading.tsx [isolated] │ │ │ ├── cart/ │ │ ├── page.tsx /cart CartClient (standalone) │ │ └── loading.tsx │ │ │ ├── checkout/ │ │ ├── page.tsx /checkout CheckoutClient (standalone) │ │ ├── loading.tsx │ │ └── success/page.tsx /checkout/success │ │ │ ├── change-password/page.tsx │ ├── pricing/page.tsx + loading.tsx │ ├── contact/page.tsx + loading.tsx │ ├── blog/page.tsx │ ├── changelog/page.tsx │ ├── roadmap/page.tsx │ ├── waitlist/page.tsx │ ├── security/page.tsx │ ├── privacy-policy/page.tsx │ ├── terms-and-conditions/page.tsx │ ├── maintenance/page.tsx │ │ │ ├── ─── STOREFRONTS ───────────────────────────────────────── │ │ │ ├── tuxedo/ │ │ ├── layout.tsx ─────────────────────── [TUXEDO CHROME] │ │ │ • Dark emerald gradient backdrop │ │ │ • Ambient emerald orb (top-right blur) │ │ │ • + per page │ │ │ • BreadcrumbList + LocalBusiness JSON-LD in metadata │ │ │ ─── INSIDE ─── │ │ │ ├── error.tsx │ │ │ ├── loading.tsx │ │ │ ├── page.tsx /tuxedo │ │ │ ├── stops/ │ │ │ │ ├── page.tsx /tuxedo/stops │ │ │ │ ├── loading.tsx │ │ │ │ └── [slug]/ │ │ │ │ ├── page.tsx /tuxedo/stops/:slug │ │ │ │ ├── error.tsx │ │ │ │ └── loading.tsx │ │ │ ├── about/ │ │ │ │ ├── layout.tsx │ │ │ │ ├── page.tsx /tuxedo/about │ │ │ │ ├── error.tsx │ │ │ │ └── loading.tsx │ │ │ ├── faq/ │ │ │ │ ├── layout.tsx │ │ │ │ ├── page.tsx /tuxedo/faq │ │ │ │ ├── error.tsx │ │ │ │ └── loading.tsx │ │ │ ├── contact/ │ │ │ │ ├── layout.tsx │ │ │ │ ├── page.tsx /tuxedo/contact │ │ │ │ ├── error.tsx │ │ │ │ └── loading.tsx │ │ │ ├── products/ │ │ │ │ └── [slug]/page.tsx /tuxedo/products/:slug │ │ │ └── time-clock/page.tsx /tuxedo/time-clock │ │ │ ├── indian-river-direct/ │ │ ├── layout.tsx ─────────────────── [IRD CHROME] │ │ │ • Light blue/white glass gradient backdrop │ │ │ • Three decorative blue orbs │ │ │ • + per page │ │ │ • LocalBusiness (FL, family-owned 1985) JSON-LD │ │ │ ─── INSIDE ─── │ │ │ ├── error.tsx │ │ │ ├── loading.tsx │ │ │ ├── page.tsx /indian-river-direct │ │ │ ├── stops/ │ │ │ │ ├── page.tsx │ │ │ │ └── [slug]/ │ │ │ │ ├── page.tsx │ │ │ │ ├── error.tsx │ │ │ │ └── loading.tsx │ │ │ ├── about/ (layout + page + error + loading) │ │ │ ├── faq/ (layout + page + error + loading) │ │ │ └── contact/ (layout + page + error + loading) │ │ │ ├── ─── ADMIN ────────────────────────────────────────────── │ │ │ └── admin/ │ ├── layout.tsx ───────────────────── [ADMIN SHELL] │ │ • dynamic = "force-dynamic" (reads cookies) │ │ • getAdminUser() → AdminSidebar + content area │ │ • ToastProvider + ToastContainer │ │ • Sidebar persists across all /admin/* routes │ │ • Parchment background via .admin-section │ │ ─── INSIDE ─── │ │ ├── error.tsx Admin error page │ │ ├── loading.tsx Skeleton: header + stats + table │ │ ├── page.tsx /admin (Dashboard) │ │ │ │ │ ├── orders/ │ │ │ ├── page.tsx /admin/orders │ │ │ ├── [id]/page.tsx /admin/orders/:id │ │ │ └── new/page.tsx /admin/orders/new │ │ │ │ │ ├── products/ │ │ │ ├── page.tsx /admin/products │ │ │ ├── [id]/page.tsx │ │ │ ├── new/page.tsx │ │ │ └── import/page.tsx │ │ │ │ │ ├── stops/ │ │ │ ├── page.tsx /admin/stops │ │ │ ├── [id]/page.tsx │ │ │ └── new/page.tsx │ │ │ │ │ ├── communications/ │ │ │ ├── loading.tsx │ │ │ ├── page.tsx /admin/communications │ │ │ ├── compose/page.tsx │ │ │ ├── campaigns/[id]/page.tsx │ │ │ ├── contacts/page.tsx │ │ │ ├── logs/page.tsx │ │ │ ├── segments/page.tsx │ │ │ ├── settings/page.tsx │ │ │ ├── templates/page.tsx │ │ │ ├── templates/[id]/page.tsx │ │ │ ├── analytics/page.tsx │ │ │ ├── abandoned-carts/page.tsx │ │ │ └── welcome-sequence/page.tsx │ │ │ │ │ ├── settings/ (sub-tree of 13 pages: billing, brand, │ │ │ payments, shipping, ai, apps, │ │ │ integrations, square-sync, ...) │ │ │ │ │ ├── route-trace/ (lots, lookup, settings) │ │ ├── me/ │ │ ├── pickup/ Store-employee landing │ │ ├── sales/import/ │ │ ├── import/ │ │ ├── analytics/ │ │ ├── command-center/ │ │ ├── advanced/ │ │ ├── reports/ │ │ ├── shipping/ │ │ ├── taxes/ │ │ ├── time-tracking/ │ │ ├── water-log/ (sub-tree) │ │ ├── wholesale/ │ │ └── launch-checklist/ │ │ │ ├── wholesale/ (separate sub-app, not under admin/layout) │ │ ├── login/page.tsx │ │ ├── portal/page.tsx │ │ ├── register/page.tsx │ │ ├── employee/page.tsx │ │ └── payment/{success,cancel}/page.tsx │ │ │ ├── water/ (separate sub-app, not under admin/layout) │ │ ├── page.tsx │ │ ├── loading.tsx │ │ └── admin/ (login + page) │ │ │ ├── ird/time-clock/ (time-clock for IRD employees) │ ├── trace/[lotNumber]/page.tsx (public route-trace lookup) │ └── protected-example/page.tsx (smoke test for middleware) ``` ## Key nesting rules 1. **The root `app/layout.tsx` is the only one that renders `` and ``**. Every other layout is a child of it. 2. **A `layout.tsx` in a folder wraps every `page.tsx` in that folder and all subfolders**. Children swap; layout state persists. 3. **`loading.tsx` and `error.tsx` are per-folder** — Next.js shows them when navigating to a sibling `page.tsx`. They re-render as you move between sections. 4. **Storefronts are fully isolated**: each `tuxedo/layout.tsx` and `indian-river-direct/layout.tsx` provides its own backdrop and lets individual pages render their own `StorefrontHeader` / `StorefrontFooter`. 5. **Admin is a single SPA-feeling shell**: navigating between `/admin/orders` and `/admin/products` keeps the `AdminSidebar` mounted, only the content area swaps. ## Things that DO NOT yet nest (gaps) | Page | Issue | Fix | |---|---|---| | `/cart` | No shared layout with `/checkout` | Add `app/(shop)/layout.tsx` route group to wrap both | | `/wholesale/*` | Wholesale pages have no shared layout | Add `app/wholesale/layout.tsx` with auth gate + nav | | `/water/*` | Water pages have no shared layout | Add `app/water/layout.tsx` | | `/admin/communications/*` | 13 pages share no sub-layout | Could add `app/admin/communications/layout.tsx` with section tabs | | `/admin/settings/*` | 13 settings pages share no sub-layout | Add `app/admin/settings/layout.tsx` with settings sidebar | ## How to add a nested layout ```bash # Example: add a settings sub-layout mkdir -p src/app/admin/settings # Create src/app/admin/settings/layout.tsx # It will wrap every page under /admin/settings/* automatically. ``` ```tsx // src/app/admin/settings/layout.tsx export default function SettingsLayout({ children }: { children: React.ReactNode }) { return (
{/* sticks while content swaps */}
{children}
); } ``` ## How to add parallel routes (modals as overlays) ```bash mkdir -p 'src/app/@modal' mkdir -p 'src/app/admin/products/(.)new' # intercepts /admin/products/new # Layout returns <>{children}{modal} ``` This makes `/admin/products/new` open as a modal over `/admin/products` instead of a hard navigation.