Parallel and Intercepting Routes in Next.js
How parallel slots and intercepting routes power dashboards, modals, and tabbed UIs in the Next.js App Router — the file conventions, when to reach for each, and the patterns that hold up in production.
What you'll learn
- ✓How @slot folders enable multiple page views in one layout
- ✓How (.) (..) (...) intercepting conventions work
- ✓The classic "modal with shareable URL" pattern
- ✓Where these features bite back
Prerequisites
- •Solid understanding of layouts and route segments
What and Why
Most apps eventually need two things that traditional file-based routers struggle with. First, multiple views in one layout — a dashboard with a feed, a sidebar, and a notifications panel that each have their own loading and error states. Second, modals that share a URL — clicking a photo opens a modal overlay, but visiting that same URL directly shows the full page.
Parallel routes and intercepting routes are the App Router’s answers. Parallel routes let one layout render several independently-streamed pages. Intercepting routes let one URL render as a modal in one context and as a full page in another.
Mental Model
A parallel route is a folder prefixed with @. Inside a layout, each @slot becomes a prop. So app/dashboard/layout.tsx receives children, @analytics, and @team if those folders exist. Each slot has its own page.tsx, loading.tsx, and error.tsx. They render concurrently, fail independently, and stream separately.
An intercepting route uses parentheses-with-dots: (.) for same level, (..) one level up, (..)(..) two levels up, (...) from the root. The folder @modal/(..)photo/[id] says: “when the user navigates to /photo/123 from this layout, render this page instead of the real one.” Direct visits still hit the real route.
Hands-on Example
Build a photo grid where clicking a photo opens a modal, but /photo/123 directly loads the full page.
app/
layout.tsx // renders {children} and {modal}
@modal/
default.tsx // returns null when no modal active
(..)photo/
[id]/
page.tsx // the modal UI
page.tsx // the photo grid
photo/
[id]/
page.tsx // the full photo page
The root layout consumes both slots:
// app/layout.tsx
export default function RootLayout({ children, modal }) {
return (
<html><body>
{children}
{modal}
</body></html>
);
}
In the grid, link with <Link href="/photo/123">. Because the user is coming from the root layout, the (..)photo/[id] intercepting route matches first and renders the modal. Reloading the page or sharing the URL bypasses the interception and renders the full app/photo/[id]/page.tsx.
Click <Link href="/photo/123"> from grid
|
v
Router checks for intercepting match in active layout
|-- found: @modal/(..)photo/[id]/page.tsx
|
v
[Render] RootLayout
children = grid (unchanged)
modal = ModalPhoto(123) <- overlay
Direct visit / reload of /photo/123
|
v
No interception (no active layout slot to host it)
|
v
[Render] RootLayout
children = app/photo/[id]/page.tsx <- full page
modal = @modal/default.tsx (null) For a pure parallel-routes case (no interception), think of a dashboard:
app/dashboard/
layout.tsx // uses {children}, {analytics}, {team}
page.tsx // main feed
@analytics/page.tsx
@team/page.tsx
Each slot streams independently. A slow @analytics does not block @team.
Common Pitfalls
The first gotcha is missing default.tsx. Every parallel slot needs a default.tsx to handle navigations where that slot has no explicit match — without it, a soft navigation can blank the slot or 404 the whole layout. The fix is a one-line export default function Default() { return null; }.
Second, counting segments wrong with (..). The dots count route segments, not file-system folders. Groups like (marketing) do not count. People burn an hour on this; double-check by reading the URL path the route resolves to.
Third, interception only on soft navigation. A full reload, an external link, or an opened-in-new-tab URL all bypass interception. Your modal page must therefore be a reasonable standalone page too. Design for both at once.
Fourth, state sync between slots. Slots render independently, so a write in one (e.g. starring a photo in @modal) does not automatically refresh the other. Use revalidatePath or router.refresh() after mutations.
Best Practices
Reach for parallel routes when slots have genuinely independent data and lifecycles — dashboards, split panels, conditional UI based on auth state. Always ship a default.tsx per slot from day one. Use intercepting routes for the modal-with-URL pattern and resist using them as a general routing trick; the cognitive overhead is real. Keep the intercepted page and the real page rendering the same data so refresh-vs-click feels consistent. Finally, write a short comment in each @slot README pointing to where the slot is consumed — six months later you will thank yourself.
Wrap-up
Parallel routes give you concurrent, independently-streamed views in one layout. Intercepting routes give you context-aware rendering for the same URL. Used carefully, they unlock UI patterns that used to require client-side state machines and brittle modals. Used carelessly, the file conventions become a maze — so start small, name slots clearly, and always include the defaults.
Related articles
- Next.js Error Boundaries in the Next.js App Router
How error.tsx, global-error.tsx, and not-found.tsx work in the Next.js App Router — when each one fires, how segments isolate failures, and patterns for recovery and observability.
- Next.js Next.js Dynamic Routes and Catch-All Segments Explained
Master dynamic routes, catch-all segments, and optional catch-alls in Next.js. Learn how the file-based router maps URLs to components with concrete examples.
- Next.js The Next.js App Router: Pages, Layouts, and Routing
A practical guide to the Next.js App Router — file-based routing, nested layouts, dynamic segments, client-side navigation with Link and useRouter, and the special loading and error files.
- Web Next.js App Router vs Pages Router
Compare the Next.js App Router and Pages Router: routing, data fetching, layouts, server components, and how to decide for new and existing projects.