Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Parallel Routes let a shared Next.js App Router layout render multiple route branches at the same time. You create named slots with @folder directories, receive those slots as layout props, and can give each branch its own pages, loading UI, error UI, and navigation state. The feature is especially useful for dashboards, conditional layouts, persistent panels, and URL-addressable modals.

This guide uses Next.js 13-compatible examples. Parallel and Intercepting Routes were introduced in the Next.js 13 line, with the feature documented in the Next.js 13.3 announcement. Current Next.js documentation retains the same core concepts, although examples and type signatures may differ between releases.

What problem do Parallel Routes solve?

A conventional layout usually renders one main route branch through children:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
export default function Layout({ children }: { children: React.ReactNode }) {
  return <main>{children}</main>
}

Parallel Routes let the same layout render several independently selected branches:

export default function Layout({
  children,
  sidebar,
  content,
}: {
  children: React.ReactNode
  sidebar: React.ReactNode
  content: React.ReactNode
}) {
  return (
    <div className="shell">
      {sidebar}
      {content}
      {children}
    </div>
  )
}

This is more than placing two React components beside each other. Each branch can have route state, nested pages, loading boundaries, error boundaries, and browser-history behavior. If two regions are purely presentational and do not need independent routes, ordinary components are usually simpler.

See the Next.js 13 Parallel Routes documentation and the Next.js 13.3 announcement.

Slots and the @folder convention

A named slot is a route branch created by a directory beginning with @:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
app/
├── layout.tsx
├── page.tsx
├── @team/
└── @analytics/

The slot name becomes a prop on the layout. The @ prefix is removed from the prop name:

export default function Layout({
  children,
  team,
  analytics,
}: {
  children: React.ReactNode
  team: React.ReactNode
  analytics: React.ReactNode
}) {
  return (
    <>
      {children}
      {team}
      {analytics}
    </>
  )
}
  • @team is a slot named team.
  • The layout must render {'{team}'} or the branch will not appear.
  • children is the implicit default slot.
  • Slot names do not appear in the browser URL.
  • Slots still affect the route tree and layout composition.

For example, app/@team/settings/page.tsx maps to the /settings path from the slot’s perspective, not /team/settings. Do not treat a slot like an ordinary URL folder.

Build a minimal dashboard

Create this structure:

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

app/@team/page.tsx:

export default function Team() {
  return <section>Team overview</section>
}

app/@analytics/page.tsx:

export default function Analytics() {
  return <section>Analytics overview</section>
}

app/layout.tsx:

export default function Layout({
  children,
  team,
  analytics,
}: {
  children: React.ReactNode
  team: React.ReactNode
  analytics: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <main>{children}</main>
        <aside>{team}</aside>
        <section>{analytics}</section>
      </body>
    </html>
  )
}

Both @team/settings/page.tsx and @analytics/settings/page.tsx participate at /settings. Plan these combinations carefully: two slots can otherwise resolve to conflicting pages or produce an unintuitive route tree.

Dynamic segments, catch-all routes, and route groups

Slots can contain dynamic and catch-all segments:

app/@team/[id]/page.tsx
app/@auth/[...catchAll]/page.tsx
app/(dashboard)/@sidebar/

Route groups such as (dashboard) organize routes without adding a URL segment. Neither route groups nor slots consume URL segments, but they serve different purposes: route groups organize the tree, while slots provide named layout branches.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

default.tsx: the fallback for an unmatched slot

Next.js can preserve the active subpage of a slot during soft client-side navigation. After a refresh, direct URL entry, or other hard navigation, the router has only the URL and may not be able to reconstruct every slot’s previous state. A slot’s default.tsx supplies a fallback:

// app/@auth/default.tsx
export default function Default() {
  return null
}
Situation Typical behavior
Soft client-side navigation Next.js can preserve active slot state.
Refresh or direct URL entry Next.js reconstructs state from the URL.
Matching slot route exists That route renders.
No match, with default.tsx The default fallback renders.
No match and no default A 404 may render.

default.tsx is not a universal empty-state component. It is specifically a fallback for an unmatched slot state. The implicit children slot may also need a default file when the router cannot recover the active parent page. See the current Parallel Routes reference.

Soft navigation versus hard navigation

A normal App Router link performs client-side navigation:

import Link from 'next/link'

export default function Navigation() {
  return <Link href="/settings">Settings</Link>
}

During this soft navigation, Next.js can retain a slot’s previously active subpage when the new URL does not directly change that branch.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A hard navigation includes refreshing the browser, pasting a URL into the address bar, opening a deep link directly, or opening it in a new tab. The previous in-memory slot state is unavailable, so Next.js uses the URL and then falls back to default.tsx when a slot is unmatched.

Hard-navigation diagnostic checklist

  1. Confirm the slot has a default.tsx at the correct level.
  2. Check that the layout prop matches the slot name without the @.
  3. Verify that the layout actually renders the prop.
  4. Check whether a nested or catch-all route should handle the URL.
  5. Look for conflicting pages across parallel slots.
  6. Determine whether the problem occurs only after refresh.

Independent loading and error states

Each slot can contain its own boundaries:

app/
├── @analytics/
│   ├── loading.tsx
│   ├── error.tsx
│   └── page.tsx
└── @team/
    ├── loading.tsx
    ├── error.tsx
    └── page.tsx

This allows analytics to display its own loading skeleton while team data loads, or one region to show an error UI without replacing every descendant of the dashboard. The boundaries apply according to their position in the route tree; an error in a parent layout can still affect all of its descendants.

Parallel Routes support separate loading and error experiences, but they do not guarantee complete isolation across every rendering, caching, or parent-layout failure.

Conditional route branches

A shared layout can choose which slot to render:

import { getUser } from '@/lib/auth'

export default function Layout({
  dashboard,
  login,
}: {
  dashboard: React.ReactNode
  login: React.ReactNode
}) {
  const user = getUser()
  return user ? dashboard : login
}

This pattern can support authenticated versus unauthenticated branches, workspaces, account states, and role-specific panels. However, hiding a slot is not authorization. Protect data access and server actions independently, and account for the authentication lookup’s effect on dynamic rendering and caching.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Reading the active segment inside a slot

The client hooks useSelectedLayoutSegment and useSelectedLayoutSegments accept a parallel-route key:

'use client'

import { useSelectedLayoutSegment } from 'next/navigation'

export default function TeamNav() {
  const activeSegment = useSelectedLayoutSegment('team')
  return <p>Active team segment: {activeSegment}</p>
}

The key is the slot name without @. These hooks are useful for active dashboard tabs, slot-specific sidebars, breadcrumbs, and contextual controls. They must run in a Client Component, and the hook can return null when there is no active child segment or the hook is placed at the wrong level.

See the current file-convention reference for the parallel route key behavior.

URL-addressable modals: Parallel Routes plus Intercepting Routes

Parallel Routes alone do not create the complete modal pattern. Combine a modal slot with an Intercepting Route:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
app/
├── layout.tsx
├── login/
│   └── page.tsx
└── @auth/
    ├── default.tsx
    └── (.)login/
        └── page.tsx

The regular page remains directly accessible:

// app/login/page.tsx
import { Login } from '@/app/ui/login'

export default function Page() {
  return <Login />
}

The intercepted version wraps the same content in a modal:

// app/@auth/(.)login/page.tsx
import { Modal } from '@/components/modal'
import { Login } from '@/app/ui/login'

export default function LoginModal() {
  return (
    <Modal>
      <Login />
    </Modal>
  )
}

Render the slot from the layout:

export default function Layout({
  children,
  auth,
}: {
  children: React.ReactNode
  auth: React.ReactNode
}) {
  return (
    <>
      {children}
      {auth}
    </>
  )
}

The (.) matcher means the route is intercepted at the same route level. During the intended soft-navigation flow, /login can appear as an overlay while the underlying page remains visible. A direct visit or refresh normally renders the standalone app/login/page.tsx route instead. See the Intercepting Routes documentation.

Closing the modal

'use client'

import { useRouter } from 'next/navigation'

export function CloseButton() {
  const router = useRouter()
  return <button onClick={() => router.back()}>Close</button>
}

router.back() returns to the previous history entry and is appropriate when the modal was opened through navigation. A link is more predictable when a known destination is required:

import Link from 'next/link'

export function CloseLink() {
  return <Link href="/">Close</Link>
}

Back navigation can behave unexpectedly when the modal URL was opened directly or the history stack does not represent the expected underlying page. A catch-all route can also be useful when the modal slot must absorb unrelated paths and become empty rather than retaining stale content:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
app/@auth/[...catchAll]/page.tsx

In the documented modal pattern, a catch-all route takes precedence over default.js for the relevant matching behavior.

Modal accessibility is separate from routing

Parallel Routes make a modal route-aware; they do not make the dialog accessible. The modal component should provide:

  • Focus trapping and focus restoration.
  • Escape-key dismissal.
  • role="dialog" and aria-modal="true".
  • An accessible label.
  • Background interaction blocking and scroll locking.
  • A usable full-page version for direct links and refreshes.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Server and Client Components

App Router pages and layouts are Server Components by default. Keep data fetching and route composition on the server where practical. Navigation hooks such as useRouter and useSelectedLayoutSegment require Client Components.

Prefer small client boundaries for modal controls, active-tab indicators, and click handlers rather than marking an entire layout 'use client' because one button needs a hook. The Next.js 13 App Router documentation explains the Server Component default.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Common failures and fixes

A slot renders nothing

  • Confirm @analytics maps to the analytics prop.
  • Confirm the layout renders {'{analytics}'}.
  • Check that the slot contains a matching page.tsx.
  • Check whether a conditional branch is intentionally hiding it.

Refresh produces a 404

Add a fallback at the slot level:

app/@slot/default.tsx
export default function Default() {
  return null
}

If the slot should absorb arbitrary paths, consider a catch-all route. A default file cannot repair every invalid URL or route conflict.

The modal works through links but not on refresh

This is normally expected. Interception is designed for the soft-navigation pattern; direct access and refresh should render the full route page. Test both outcomes deliberately rather than treating the difference as a defect.

The wrong modal remains visible

Check whether the slot retained its previous soft-navigation state, whether a catch-all route is needed, whether router.back() is returning to an unexpected history entry, and whether the ordinary and intercepted routes align.

Two parallel pages conflict

Slots do not add URL segments, so different slots can resolve to the same effective route combination. Also check static and dynamic behavior: current documentation notes that if one slot at a level is dynamic, all slots at that level must be dynamic.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Error boundaries do not isolate a failure

Check boundary placement. An error.tsx inside one slot covers that slot’s route subtree, while a failure in a parent layout can affect multiple branches.

When should you use Parallel Routes?

Requirement Best first choice
Several route-aware regions in one layout Parallel Routes
Shareable overlay during client navigation Parallel Routes plus Intercepting Routes
One active content branch with shared chrome Nested layouts
Simple UI state with no URL requirement Local or client state
State naturally represented in the URL Search parameters

Use Parallel Routes when

  • Dashboard regions need independent route state.
  • Sections need separate loading or error experiences.
  • A modal or panel should be represented by a URL.
  • Back and forward navigation should participate in the UI state.
  • A layout needs conditional route branches.

Prefer simpler alternatives when

  • The regions are purely presentational.
  • A local tab or modal state is sufficient.
  • There is one main content branch.
  • A complex slot tree would be harder to maintain than ordinary components.

Implementation checklist

  • Use @ directories for named slots.
  • Match layout props to slot names without the @.
  • Render every slot prop that should appear.
  • Remember that slots do not appear in URLs.
  • Add default.tsx where unmatched hard-navigation states need a fallback.
  • Test links, refreshes, direct deep links, new tabs, and back/forward navigation.
  • Keep modal content usable as a full page.
  • Place loading and error boundaries at the intended route level.
  • Enforce authentication independently of conditional rendering.
  • Implement dialog accessibility separately.

Conclusion

Parallel Routes are best understood as named route branches passed into a shared layout. The @folder convention creates slots, default.tsx handles unmatched slot state during hard navigation, and Intercepting Routes turn those slots into URL-addressable modal experiences. They are powerful for dashboards and route-aware UI regions, but ordinary components, nested layouts, query parameters, or client state are often clearer when independent routing is unnecessary.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.