/**
* Next.js 16 - Parallel Routes with Required default.js
*
* BREAKING CHANGE: Parallel routes now REQUIRE explicit default.js files.
* Without them, routes will fail during soft navigation.
*
* Directory structure:
* app/
* ├── @modal/
* │ ├── login/
* │ │ └── page.tsx
* │ └── default.tsx ← REQUIRED in Next.js 16
* ├── @feed/
* │ ├── trending/
* │ │ └── page.tsx
* │ └── default.tsx ← REQUIRED in Next.js 16
* └── layout.tsx
*/
// ============================================================================
// Example 1: Modal + Main Content (Common Pattern)
// ============================================================================
// File: app/layout.tsx
export default function RootLayout({
children,
modal,
}: {
children: React.ReactNode
modal: React.ReactNode
}) {
return (
{modal}
{children}
)
}
// File: app/@modal/login/page.tsx
export default function LoginModal() {
return (
)
}
// File: app/@modal/default.tsx (REQUIRED)
export default function ModalDefault() {
return null // No modal shown by default
}
// File: app/page.tsx
export default function HomePage() {
return (
)
}
// ============================================================================
// Example 2: Dashboard with Multiple Panels
// ============================================================================
// File: app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
notifications,
activity,
}: {
children: React.ReactNode
analytics: React.ReactNode
notifications: React.ReactNode
activity: React.ReactNode
}) {
return (
)
}
// File: app/dashboard/@analytics/overview/page.tsx
export default async function AnalyticsOverview() {
const stats = await fetch('https://api.example.com/stats').then(r => r.json())
return (
Analytics
Page Views: {stats.pageViews}
Unique Visitors: {stats.uniqueVisitors}
)
}
// File: app/dashboard/@analytics/default.tsx (REQUIRED)
export default function AnalyticsDefault() {
return (
Analytics
No analytics data available
)
}
// File: app/dashboard/@notifications/default.tsx (REQUIRED)
export default function NotificationsDefault() {
return (
Notifications
No new notifications
)
}
// File: app/dashboard/@activity/default.tsx (REQUIRED)
export default function ActivityDefault() {
return (
Recent Activity
No recent activity
)
}
// ============================================================================
// Example 3: E-commerce with Product + Reviews
// ============================================================================
// File: app/products/[id]/layout.tsx
export default function ProductLayout({
children,
reviews,
recommendations,
}: {
children: React.ReactNode
reviews: React.ReactNode
recommendations: React.ReactNode
}) {
return (
{children}
{reviews}
{recommendations}
)
}
// File: app/products/[id]/@reviews/page.tsx
export default async function ProductReviews({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const reviews = await fetch(`https://api.example.com/products/${id}/reviews`)
.then(r => r.json())
return (
Reviews
{reviews.map((review: { id: string; rating: number; comment: string }) => (
-
⭐ {review.rating}/5
{review.comment}
))}
)
}
// File: app/products/[id]/@reviews/default.tsx (REQUIRED)
export default function ReviewsDefault() {
return (
)
}
// File: app/products/[id]/@recommendations/default.tsx (REQUIRED)
export default function RecommendationsDefault() {
return (
Recommendations
Loading recommendations...
)
}
// ============================================================================
// Example 4: Auth-Gated Content
// ============================================================================
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
// File: app/@auth/default.tsx (REQUIRED)
export default async function AuthDefault() {
const cookieStore = await cookies()
const isAuthenticated = cookieStore.get('auth')?.value
if (!isAuthenticated) {
redirect('/login')
}
return null
}
// File: app/@auth/profile/page.tsx
export default async function ProfilePage() {
const cookieStore = await cookies()
const userId = cookieStore.get('userId')?.value
const user = await fetch(`https://api.example.com/users/${userId}`)
.then(r => r.json())
return (
Profile
Name: {user.name}
Email: {user.email}
)
}
// ============================================================================
// Example 5: Conditional Rendering Based on Slot
// ============================================================================
// File: app/layout.tsx
export default function Layout({
children,
banner,
}: {
children: React.ReactNode
banner: React.ReactNode
}) {
// Only show banner on specific pages
const showBanner = true // Determine based on route
return (
{showBanner && banner}
{children}
)
}
// File: app/@banner/sale/page.tsx
export default function SaleBanner() {
return (
🎉 50% OFF SALE! Use code SALE50
)
}
// File: app/@banner/default.tsx (REQUIRED)
export default function BannerDefault() {
return null // No banner by default
}
// ============================================================================
// Example 6: Loading States with Parallel Routes
// ============================================================================
// File: app/dashboard/@analytics/loading.tsx
export default function AnalyticsLoading() {
return (
Analytics
Loading analytics...
)
}
// File: app/dashboard/@notifications/loading.tsx
export default function NotificationsLoading() {
return (
)
}
// ============================================================================
// Example 7: Error Boundaries with Parallel Routes
// ============================================================================
// File: app/dashboard/@analytics/error.tsx
'use client'
export default function AnalyticsError({
error,
reset,
}: {
error: Error
reset: () => void
}) {
return (
Analytics
Failed to load analytics
)
}
// ============================================================================
// Migration Guide: Next.js 15 → Next.js 16
// ============================================================================
/**
* BREAKING CHANGE: default.js is now REQUIRED for all parallel routes
*
* ❌ BEFORE (Next.js 15):
* app/
* ├── @modal/
* │ └── login/
* │ └── page.tsx
* └── layout.tsx
*
* This worked in Next.js 15. If no matching route, Next.js rendered nothing.
*
* ✅ AFTER (Next.js 16):
* app/
* ├── @modal/
* │ ├── login/
* │ │ └── page.tsx
* │ └── default.tsx ← REQUIRED! Will error without this
* └── layout.tsx
*
* Why the change?
* Next.js 16 changed how parallel routes handle soft navigation. Without
* default.js, unmatched slots will error during client-side navigation.
*
* What should default.tsx return?
* - return null (most common - no UI shown)
* - return (loading placeholder)
* - redirect() to another route
* - return fallback UI
*/
// ============================================================================
// Common Patterns for default.tsx
// ============================================================================
// Pattern 1: Null (no UI)
export function DefaultNull() {
return null
}
// Pattern 2: Loading skeleton
export function DefaultSkeleton() {
return (
)
}
// Pattern 3: Fallback message
export function DefaultFallback() {
return (
)
}
// Pattern 4: Redirect
import { redirect } from 'next/navigation'
export function DefaultRedirect() {
redirect('/dashboard')
}
/**
* Summary:
*
* Parallel Routes in Next.js 16:
* 1. ✅ Use @folder convention for parallel slots
* 2. ✅ MUST include default.tsx for each @folder
* 3. ✅ default.tsx handles unmatched routes during navigation
* 4. ✅ Can have loading.tsx for loading states
* 5. ✅ Can have error.tsx for error boundaries
*
* Common use cases:
* - Modals + main content
* - Dashboard panels
* - Product + reviews/recommendations
* - Conditional banners
* - Auth-gated content
*
* Best practices:
* - Keep default.tsx simple (usually return null)
* - Use loading.tsx for better UX
* - Use error.tsx for error handling
* - Test soft navigation (client-side routing)
*/