Techniques for Dynamic UI Composition
Parallel routes and intercepting routes, and how they combine to build dashboard- and modal-style UIs without hand-rolled client state.
View the live demo →Not making everyone wait for the slowest dish
Think of a restaurant where the kitchen is preparing your order. When the drinks are ready, the server brings them to the table — you don't have to wait for the entire meal to be finished. That's streaming: the server can send parts of a route as they become ready, rather than waiting for the entire thing to finish.
Modern applications often have multiple pieces of UI that don't need to become ready at the same time — a dashboard's main content, an activity feed, a notifications panel. The rest of this pattern is about the two Next.js features that make that possible.
Parallel routes — multiple places at the table.
Streaming — don't wait for every dish before serving what's ready.
Intercepting routes — serve something in the current context instead of taking the user somewhere else.
Multiple places at the table
Next.js gives those pieces separate slots within a shared layout, using the @ folder convention. A dashboard might have:
- Main content
- Live activity feed
- Notifications panel
Each slot can have its own loading and error UI, and streaming allows those pieces to reach the client progressively as their work completes.
Parallel routes are what make multiple UI areas possible in the same layout. The early-arrival behavior itself comes from streaming, not from parallel routes on their own.
Presenting a route without navigating away
A route can be presented within the current context instead of replacing it entirely. The classic example is a modal: clicking a task opens its details as an overlay on the dashboard, while navigating directly to that same URL renders the standalone page.
- Navigate from the dashboard → task opens in a modal
- Navigate directly to the task URL → task renders as a full page
The URL represents the same resource, but the UI can present it differently depending on how the user arrived there.
Why combine them?
Parallel routes answer: what UI areas can exist together?
Intercepting routes answer: how is a route presented during navigation?
Together, they let Next.js manage a composition that would otherwise require a combination of client-side state, routing logic, and conditional rendering. The result is a dashboard where different pieces can become ready independently, while navigation can open those pieces in context rather than replacing the entire page.
The dashboard demo's folder structure
Here's the folder structure behind the live demo: parallel routes for the sidebar and modal slot, and an intercepting route that opens a task's details in an overlay.
app/
└── dashboard/
├── layout.tsx
├── page.tsx
│
├── @sidebar/
│ ├── page.tsx
│ ├── loading.tsx
│ ├── error.tsx
│ └── default.tsx
│
├── @modal/
│ ├── default.tsx
│ ├── error.tsx
│ └── (.)tasks/[id]/
│ └── page.tsx
│
└── tasks/
└── [id]/
└── page.tsx(.) counts route segments, not filesystem folders — @modal isn't itself a route segment, so (.)tasks/[id] still intercepts from the dashboard level, even though it's nested inside @modal/.
There are three important pieces here.
The sidebar is a parallel route. @sidebar gives the dashboard another UI slot that can resolve independently from the main children content.
The modal is an intercepted route. (.)tasks/[id] intercepts navigation to a task and renders it inside the @modal slot while the dashboard remains visible.
The task page is the direct route. If the user visits /dashboard/tasks/[id] directly, or reloads that URL, Next.js renders the dedicated task page instead of the modal presentation.
The same task URL therefore supports two experiences: contextual navigation from the dashboard, and a standalone page when accessed directly. The live demo at the top of this page puts all three pieces into practice.