From 9458fd050664f1f726b6b2d2cd210385402bf733 Mon Sep 17 00:00:00 2001 From: Tyler Date: Tue, 16 Jun 2026 23:29:16 -0600 Subject: [PATCH] docs: add nested layout skeleton map Visualizes the current layout nesting in the App Router, identifies gaps where shared sub-layouts could be added (cart/checkout, wholesale, water, admin/communications, admin/settings), and shows how to add a new nested layout or parallel route for modal-style navigation. --- docs/NESTED_LAYOUTS.md | 255 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 docs/NESTED_LAYOUTS.md diff --git a/docs/NESTED_LAYOUTS.md b/docs/NESTED_LAYOUTS.md new file mode 100644 index 0000000..6ef8263 --- /dev/null +++ b/docs/NESTED_LAYOUTS.md @@ -0,0 +1,255 @@ +# 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.