Next.js 14 Parallel Routes: Complete Usage Guide
How to use parallel routes (@slot) in Next.js 14 App Router for simultaneous page rendering and advanced layouts
Next.js 14 Parallel Routes: Complete Usage Guide
Introduction
Parallel routes are one of the most powerful and least understood features in Next.js 14 App Router. They allow you to render multiple pages simultaneously in the same layout using the @ prefix syntax. This enables patterns like simultaneous dashboard views, split-screen layouts, and isolated error handling per route segment.
I first used parallel routes to build a split-view documentation tool where the table of contents and the content both loaded independently. The user experience improvement was dramatic. This post explains everything I learned.
Environment
- OS: Windows 11
- Node.js: v20.10.0
- Next.js: 14.0.4
- React: 18.2.0
Problem
Traditional nested routing forces sequential rendering. If a sidebar and main content area are separate route segments, they must both resolve before the page is useful. Slow API calls in one segment block the entire page.
Traditional approach:
/dashboard
βββ layout.tsx (renders + )
βββ sidebar/page.tsx (slow API call - 2s)
βββ analytics/page.tsx (fast - 200ms)
Result: User waits 2s for the entire pageParallel routes approach:
/dashboard
βββ layout.tsx (renders @sidebar + @analytics)
βββ @sidebar/page.tsx (loads independently)
βββ @analytics/page.tsx (loads independently)
Result: Both segments load simultaneouslySolution
Basic parallel routes setup:
app/
βββ dashboard/
β βββ layout.tsx
β βββ @sidebar/
β β βββ page.tsx
β β βββ loading.tsx
β βββ @analytics/
β β βββ page.tsx
β β βββ loading.tsx
β βββ page.tsx (default slot)
βββ layout.tsx// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
sidebar,
analytics,
}: {
children: React.ReactNode
sidebar: React.ReactNode
analytics: React.ReactNode
}) {
return (
{children}
)
}Independent loading states for each slot:
// app/dashboard/@sidebar/loading.tsx
export default function SidebarLoading() {
return (
)
}// app/dashboard/@analytics/loading.tsx
export default function AnalyticsLoading() {
return (
)
}Error boundaries per slot:
// app/dashboard/@sidebar/error.tsx
'use client'
export default function SidebarError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
Sidebar failed to load
{error.message}
)
}Default slot content:
// app/dashboard/@analytics/page.tsx
// This is the default content when the route doesn't match @analytics
export default function AnalyticsDefault() {
return (
Analytics
Select a metric to view details
)
}Active route matching with parallel routes:
// app/dashboard/layout.tsx
import { notFound } from 'next/navigation'
export default function DashboardLayout({
children,
sidebar,
analytics,
}: {
children: React.ReactNode
sidebar: React.ReactNode
analytics: React.ReactNode
}) {
// Render 404 if none of the slots can match the route
if (!sidebar && !analytics) {
notFound()
}
return (
{children}
)
}Conditional slot rendering:
// app/(split-view)/layout.tsx
export default function SplitLayout({
left,
right,
}: {
left: React.ReactNode
right: React.ReactNode
}) {
return (
{left}
{right}
)
}Lessons Learned
Each slot is independent. Slots render simultaneously and independently. A slow slot does not block other slots.
Use
loading.tsxper slot. Each slot can have its own loading state, providing granular loading indicators.Error boundaries are per-slot. If one slot crashes, the others continue to render. This improves error resilience significantly.
Not all routes need all slots. When a URL does not match a slot's route, the slot returns
undefined. Handle this gracefully in layouts.Use parallel routes for any split-view pattern. Dashboard with sidebar, documentation with table of contents, comparison views, and chat applications all benefit from parallel routing.
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.