Parallel Routes
Parallel routes allow you to simultaneously render multiple pages in the same layout. They're defined using the @folder convention.
The Concept
Instead of one children slot, layouts can have multiple named slots:
app/
โโโ layout.tsx
โโโ page.tsx
โโโ @analytics/
โ โโโ page.tsx
โโโ @team/
โโโ page.tsx
// app/layout.tsx
export default function Layout({
children,
analytics,
team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<div>
<main>{children}</main>
<aside>
{analytics}
{team}
</aside>
</div>
)
}
Common Use Cases
Dashboard with Multiple Panels
app/dashboard/
โโโ layout.tsx
โโโ page.tsx โ Main content
โโโ @stats/
โ โโโ page.tsx โ Stats panel
โโโ @notifications/
โ โโโ page.tsx โ Notifications panel
โโโ @activity/
โโโ page.tsx โ Activity feed
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
stats,
notifications,
activity,
}: {
children: React.ReactNode
stats: React.ReactNode
notifications: React.ReactNode
activity: React.ReactNode
}) {
return (
<div className="grid grid-cols-4 gap-4">
<main className="col-span-3">{children}</main>
<aside className="space-y-4">
{stats}
{notifications}
{activity}
</aside>
</div>
)
}
Independent Loading States
Each slot can have its own loading UI:
app/dashboard/
โโโ @stats/
โ โโโ page.tsx
โ โโโ loading.tsx โ Stats skeleton
โโโ @activity/
โ โโโ page.tsx
โ โโโ loading.tsx โ Activity skeleton
Slots load independently - fast panels appear immediately.
Conditional Rendering
Show different content based on state:
// app/layout.tsx
import { getUser } from '@/lib/auth'
export default async function Layout({
children,
auth,
dashboard,
}: {
children: React.ReactNode
auth: React.ReactNode
dashboard: React.ReactNode
}) {
const user = await getUser()
return (
<div>
{user ? dashboard : auth}
{children}
</div>
)
}
Default.tsx for Unmatched Routes
When navigating between routes, slots need a fallback:
app/
โโโ layout.tsx
โโโ page.tsx โ /
โโโ @modal/
โ โโโ default.tsx โ Shows when no modal route matches
โ โโโ (.)photos/[id]/
โ โโโ page.tsx โ Shows for /photos/123
โโโ photos/[id]/
โโโ page.tsx
// app/@modal/default.tsx
export default function Default() {
return null // Don't render anything when no modal
}
Modal Pattern with Parallel Routes
The most common use case - modals that can be linked directly:
app/
โโโ layout.tsx
โโโ page.tsx
โโโ @modal/
โ โโโ default.tsx
โ โโโ (.)photos/[id]/
โ โโโ page.tsx โ Modal version
โโโ photos/
โโโ [id]/
โโโ page.tsx โ Full page version
- Click a photo link: Opens modal (via intercepting route)
- Direct link to
/photos/123: Full page - Refresh on modal: Falls back to full page
Independent Error Handling
Each slot can have its own error boundary:
app/dashboard/
โโโ @stats/
โ โโโ page.tsx
โ โโโ error.tsx โ Only catches stats errors
โโโ @activity/
โ โโโ page.tsx
โ โโโ error.tsx โ Only catches activity errors
If stats fails, activity still works.
Summary
- Parallel routes use
@folderconvention - Layout receives slots as props alongside
children - Each slot can have independent loading/error states
- Use
default.tsxfor unmatched slot routes - Great for dashboards, modals, and conditional layouts
- Slots render simultaneously, not sequentially
Up next ยท Lesson 14 of 26
Intercepting Routes
2 min read
13 of 26 lessons done

