Parallel and Intercepting Routes

Intermediate
13 min

Parallel and Intercepting Routes

Some interfaces do not fit the "one URL, one page" model: a dashboard shows several independent panels, and a photo gallery opens images in a modal that still has its own shareable URL. Parallel routes and intercepting routes are the App Router's answer. After this lesson you will be able to render multiple route slots inside one layout, give each slot its own loading and error state, and open a route as a modal while keeping full-page rendering for direct visits.

Parallel Routes: Named Slots

A folder prefixed with @ is a slot. Slots are not URL segments; they are passed to the parent layout as props, alongside the implicit children slot:

text
app/dashboard/ ├── layout.tsx ├── page.tsx # children slot ├── @analytics/ │ ├── page.tsx │ └── loading.tsx # independent loading state └── @team/ └── page.tsx
tsx
// app/dashboard/layout.tsx export default function DashboardLayout({ children, analytics, team, }: { children: React.ReactNode; analytics: React.ReactNode; team: React.ReactNode; }) { return ( <section className="grid"> {children} <aside>{analytics}</aside> <aside>{team}</aside> </section> ); }

Each slot streams independently and can have its own loading.tsx and error.tsx, so a failing analytics query does not take down the team panel. Slots can also be rendered conditionally, for example showing an @admin slot only when the current user has the right role.

default.tsx and Unmatched Slots

Slots keep their previous content during client-side navigation, but on a hard reload Next.js has to render every slot for the current URL. If a slot has no page matching that URL, it looks for default.tsx in the slot folder and renders a 404 if none exists. Add one to every slot:

tsx
// app/dashboard/@team/default.tsx export default function Default() { return null; }

Inside a Client Component, useSelectedLayoutSegment("analytics") returns the active segment of a specific slot, which is handy for tabs that highlight the current panel.

Intercepting Routes: Modals With Real URLs

An intercepting route renders another route's content inside the current layout during client-side navigation, while a direct visit or refresh still renders the original page. The folder name says which route to intercept, relative to the current segment:

| Prefix | Intercepts | |---|---| | (.) | Same level | | (..) | One level up | | (..)(..) | Two levels up | | (...) | From the app root |

Levels refer to route segments, not folders; slots such as @modal do not count.

The Photo Modal Pattern

Combine both features, as shown in the sample at the top of this lesson. Clicking a grid item navigates to /photos/123; the @modal slot intercepts it and renders the photo over the grid. The URL is shareable, and someone opening it directly gets the full photos/[id]/page.tsx instead.

tsx
// app/@modal/(.)photos/[id]/page.tsx import { Modal } from "@/components/Modal"; import { getPhoto } from "@/lib/photos"; export default async function PhotoModal({ params }: { params: Promise<{ id: string }> }) { const { id } = await params; const photo = await getPhoto(id); return ( <Modal> <img src={photo.url} alt={photo.alt} /> </Modal> ); }

The Modal component is a Client Component that closes with router.back(), which restores the grid and clears the slot. Remember the default.tsx returning null in @modal, otherwise every non-photo URL fails to render the slot.

Common mistakes

  • Omitting default.tsx and getting a 404 on refresh for pages that worked during navigation.
  • Counting @slot folders when choosing (.) versus (..); only real segments count.
  • Closing a modal with router.push("/") instead of router.back(), which leaves the interception active.
Quick Quiz
Question 1 of 3

How does a layout receive the content of `app/dashboard/@team/page.tsx`?

Key Takeaways

  • @slot folders create parallel routes passed to the layout as props, each with its own loading and error states.
  • Every slot needs a default.tsx so hard navigations can render unmatched slots.
  • Intercepting routes (.), (..), (...) show another route inside the current layout during client-side navigation.
  • Direct visits and refreshes bypass interception and render the original full page.
  • Together they implement shareable modals; close them with router.back().

Next lesson: Server Actions and Forms — mutate data on the server directly from a form without writing an API route.

Parallel and Intercepting Routes - Next.js | CodeYourCraft | CodeYourCraft