/** * Parallel Route Setup Example * * Demonstrates how to set up a complete Next.js App Router page * with multiple Parallel Route slots following the Widget-Slot Architecture. * * File structure this example represents: * * app/dashboard/ * ├── layout.tsx ← Root layout defining slot positions * ├── page.tsx ← Main content (children) * ├── @sidebar/ * │ ├── page.tsx ← Sidebar slot (imports SidebarWidget) * │ ├── loading.tsx ← Sidebar loading state * │ ├── error.tsx ← Sidebar error boundary * │ └── default.tsx ← Sidebar fallback * └── @activity/ * ├── page.tsx ← Activity slot (imports ActivityWidget) * ├── loading.tsx ← Activity loading state * ├── error.tsx ← Activity error boundary * └── default.tsx ← Activity fallback */ // ═══════════════════════════════════════════════════════════ // 1. Dashboard Layout — defines slot positions (static shell) // File: app/dashboard/layout.tsx // ═══════════════════════════════════════════════════════════ import { type ReactNode } from 'react'; interface DashboardLayoutProps { children: ReactNode; // Main content (app/dashboard/page.tsx) sidebar: ReactNode; // Parallel route: @sidebar activity: ReactNode; // Parallel route: @activity } export default function DashboardLayout({ children, sidebar, activity, }: DashboardLayoutProps) { return (
{/* Static shell — no business logic, only structure */}
{children}
{activity}
); } // ═══════════════════════════════════════════════════════════ // 2. Slot Page — connects one Widget to one Slot // File: app/dashboard/@sidebar/page.tsx // ═══════════════════════════════════════════════════════════ // import NavigationWidget from '@/Widgets/Navigation'; // // export default function SidebarSlotPage() { // return ; // } // ═══════════════════════════════════════════════════════════ // 3. Slot Loading — independent loading state per slot // File: app/dashboard/@sidebar/loading.tsx // ═══════════════════════════════════════════════════════════ export function SidebarLoading() { return (
); } // ═══════════════════════════════════════════════════════════ // 4. Slot Error — fault isolation per slot // File: app/dashboard/@sidebar/error.tsx // ═══════════════════════════════════════════════════════════ // 'use client'; ← error.tsx must be a Client Component // // interface SlotErrorProps { // error: Error & { digest?: string }; // reset: () => void; // } // // export default function SidebarError({ error, reset }: SlotErrorProps) { // return ( //
//

// This section encountered an issue. //

// //
// ); // } // ═══════════════════════════════════════════════════════════ // 5. Slot Default — fallback when no matching route // File: app/dashboard/@sidebar/default.tsx // ═══════════════════════════════════════════════════════════ export function SidebarDefault() { return (

Select a section to view navigation.

); } // ═══════════════════════════════════════════════════════════ // 6. CSS — Dashboard layout styles // File: app/dashboard/dashboard.module.css // ═══════════════════════════════════════════════════════════ /* .dashboard-layout { display: grid; grid-template-columns: 280px 1fr 320px; grid-template-rows: 1fr; min-height: 100dvh; gap: 0; } .dashboard-layout__sidebar { border-right: 1px solid var(--color-border); overflow-y: auto; } .dashboard-layout__content { padding: clamp(1rem, 3vw, 2rem); overflow-y: auto; } .dashboard-layout__activity { border-left: 1px solid var(--color-border); overflow-y: auto; } /* Responsive: stack on mobile */ @media (max-width: 768px) { .dashboard-layout { grid-template-columns: 1fr; grid-template-rows: auto 1fr auto; } .dashboard-layout__sidebar { border-right: none; border-bottom: 1px solid var(--color-border); } .dashboard-layout__activity { border-left: none; border-top: 1px solid var(--color-border); } } /* Slot loading skeleton */ .slot-loading { display: flex; flex-direction: column; gap: 0.75rem; padding: 1.5rem; } .slot-loading__skeleton { background: linear-gradient(90deg, var(--color-surface-raised) 25%, var(--color-border) 50%, var(--color-surface-raised) 75%); background-size: 200% 100%; animation: shimmer 1.5s ease-in-out infinite; border-radius: 0.25rem; } @keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } /* Slot error */ .slot-error { padding: 1.5rem; text-align: center; } .slot-error__retry { margin-top: 0.75rem; padding: 0.5rem 1rem; border: 1px solid var(--color-border); border-radius: 0.375rem; background: var(--color-surface); cursor: pointer; } */