Skip to main content

Next.js Parallel Routes

Introduction

Parallel Routes is one of Next.js's most powerful features introduced with the App Router. This feature allows you to simultaneously render multiple pages within the same layout, enabling complex UI patterns that were historically difficult to implement in React applications.

Parallel routing gives you the ability to define independent regions on a page that can be loaded, updated, and navigated independently of each other. This is particularly useful for applications that require:

  • Multi-column layouts like dashboards
  • Split screens or split views
  • Content that needs to be conditionally rendered based on certain states
  • Implementing modals without complex state management

In this tutorial, we'll explore parallel routes in depth, learn how to implement them, and see practical applications through multiple examples.

Understanding Parallel Routes

Core Concept

Parallel routes are created using a special folder naming convention with the @ symbol. These named slots correspond to props that are passed to the layout that renders them.

The @ convention allows you to map specific content to specific parts of your UI. For instance, you might have @dashboard, @analytics, and @settings folders that represent different sections of your application.

Basic Structure

Here's how a simple parallel route structure might look:

app/
├── @dashboard/
│ └── page.tsx
├── @analytics/
│ └── page.tsx
└── layout.tsx

In this structure, both @dashboard/page.tsx and @analytics/page.tsx will be passed as props to layout.tsx and can be rendered simultaneously.

How to Implement Parallel Routes

Let's start with a simple example to demonstrate parallel routing:

  1. First, create the folder structure shown above
  2. Then, implement the layout to receive the slot props:
tsx
// app/layout.tsx
export default function Layout({
dashboard,
analytics,
children
}: {
dashboard: React.ReactNode
analytics: React.ReactNode
children: React.ReactNode
}) {
return (
<div className="layout">
<div className="dashboard-container">
{dashboard}
</div>
<div className="analytics-container">
{analytics}
</div>
<div className="main-content">
{children}
</div>
</div>
);
}
  1. Create the page components for each slot:
tsx
// app/@dashboard/page.tsx
export default function DashboardPage() {
return (
<div>
<h2>Dashboard</h2>
<div className="dashboard-widgets">
{/* Dashboard content */}
<div className="widget">Active Users: 1,245</div>
<div className="widget">Revenue: $12,345</div>
</div>
</div>
);
}
tsx
// app/@analytics/page.tsx
export default function AnalyticsPage() {
return (
<div>
<h2>Analytics</h2>
<div className="chart-container">
{/* Analytics content */}
<div className="chart">Traffic Sources</div>
<div className="chart">Conversion Rates</div>
</div>
</div>
);
}
  1. Now when you navigate to the root route, both the dashboard and analytics pages will render simultaneously within the layout.

Advanced Parallel Routing Features

Conditional Rendering with default.tsx

Sometimes you might want to conditionally render content based on certain states. For this, you can use a default.tsx file to provide a fallback UI when a slot doesn't have matching route segments.

app/
├── @dashboard/
│ └── page.tsx
├── @analytics/
│ ├── page.tsx
│ └── default.tsx
└── layout.tsx
tsx
// app/@analytics/default.tsx
export default function DefaultAnalytics() {
return (
<div className="default-analytics">
<h2>Analytics</h2>
<p>Please select a report to view detailed analytics.</p>
</div>
);
}

Nested Parallel Routes

Parallel routes can also be nested within other parallel routes, enabling complex UI patterns:

app/
├── @sidebar/
│ └── page.tsx
├── @main/
│ ├── @content/
│ │ └── page.tsx
│ ├── @details/
│ │ └── page.tsx
│ └── layout.tsx
└── layout.tsx

In this structure:

  • layout.tsx receives sidebar and main props
  • @main/layout.tsx receives content and details props

You can navigate between parallel routes using the useRouter hook or the Link component. When you navigate to a new route, Next.js will only update the slots that change, preserving the state of unchanged slots.

tsx
// Component with navigation between parallel routes
import Link from 'next/link';

export default function Navigation() {
return (
<nav>
<Link href="/dashboard">Dashboard Home</Link>
<Link href="/dashboard/users">Users Dashboard</Link>
<Link href="/dashboard/analytics">Analytics Dashboard</Link>
</nav>
);
}

Practical Applications

Example 1: Dashboard Layout

One of the most common use cases for parallel routes is implementing dashboard layouts where different sections need to be loaded and updated independently.

app/
├── @sidebar/
│ └── page.tsx
├── @main/
│ └── page.tsx
├── @widgets/
│ └── page.tsx
└── layout.tsx
tsx
// app/layout.tsx
export default function DashboardLayout({
sidebar,
main,
widgets
}: {
sidebar: React.ReactNode
main: React.ReactNode
widgets: React.ReactNode
}) {
return (
<div className="dashboard-layout">
<div className="sidebar">{sidebar}</div>
<div className="main-content">{main}</div>
<div className="widget-panel">{widgets}</div>
</div>
);
}

Example 2: Modal Implementation

Parallel routes make it incredibly easy to implement modals without complex state management:

app/
├── @modal/
│ ├── login/
│ │ └── page.tsx
│ ├── signup/
│ │ └── page.tsx
│ └── default.tsx
└── layout.tsx
tsx
// app/layout.tsx
export default function Layout({
children,
modal
}: {
children: React.ReactNode
modal: React.ReactNode
}) {
return (
<div>
{children}
{/* Modal rendered alongside the main content */}
{modal}
</div>
);
}
tsx
// app/@modal/default.tsx
export default function DefaultModal() {
// Return null when no modal should be shown
return null;
}
tsx
// app/@modal/login/page.tsx
'use client';

import { useRouter } from 'next/navigation';

export default function LoginModal() {
const router = useRouter();

// Close the modal by navigating to the current route without the modal
const closeModal = () => {
router.back();
};

return (
<div className="modal-backdrop" onClick={closeModal}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<h2>Login</h2>
<form>
<input type="email" placeholder="Email" />
<input type="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
<button onClick={closeModal}>Close</button>
</div>
</div>
);
}

To navigate to the modal, you'd use:

tsx
<Link href="/login">Open Login Modal</Link>

Example 3: Multi-Step Forms with State Preservation

Parallel routes can be used to create multi-step forms where each step's state is preserved as users navigate between steps.

app/
├── @header/
│ └── page.tsx
├── @steps/
│ ├── step1/
│ │ └── page.tsx
│ ├── step2/
│ │ └── page.tsx
│ ├── step3/
│ │ └── page.tsx
│ └── layout.tsx
├── @progress/
│ └── page.tsx
└── layout.tsx

In this example, the navigation between steps would only update the @steps slot, preserving the state in the header and progress components.

Best Practices

  1. Use Descriptive Slot Names: Choose slot names that clearly describe the content or functionality they represent.

  2. Keep Slots Independent: Each parallel route should be responsible for a distinct piece of UI functionality.

  3. Handle Loading States: Use loading.tsx files within each slot folder to provide loading UI for that specific parallel route.

  4. Leverage default.tsx: Provide meaningful fallbacks for slots that might not have a matching route.

  5. Consider Client vs. Server Components: Remember that each slot can be either a client or server component depending on your needs.

Common Pitfalls

  1. Not Accounting for Navigation State: When navigating between routes, be aware that only the affected slots will re-render. This can be confusing if you expect the entire page to refresh.

  2. Overusing Parallel Routes: Not everything needs to be a parallel route. For simple UI conditionals, React's conditional rendering might be more appropriate.

  3. Missing Default Files: Without default.tsx files, your application might throw errors when a route doesn't match an expected parallel route pattern.

Summary

Next.js Parallel Routes are a powerful feature that enables complex UI patterns by allowing multiple pages to be rendered simultaneously within the same layout. By using the @ naming convention, you can create named slots that map to different parts of your UI.

Key benefits include:

  • Creating multi-column layouts and dashboards
  • Implementing modals without complex state management
  • Building split-view interfaces
  • Preserving state across navigations for independent UI sections

Parallel routes are particularly useful in applications with complex UI requirements where traditional routing solutions fall short.

Additional Resources

Exercises

  1. Create a simple dashboard with three parallel routes: a sidebar navigation, a main content area, and a user profile section.

  2. Implement a modal system that can display login, signup, and forgot password forms using parallel routes.

  3. Build a split-view email client where the list of emails and the email content are separate parallel routes that can be navigated independently.

  4. Create a nested parallel route structure for an e-commerce product page with tabs for description, reviews, and related products.

  5. Implement error handling and loading states for each parallel route in a dashboard application.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)