React
Parallel routes and slots in the App Router
`@name` slots render independent panels into one page. Structure, layout, the `default.tsx` constraint and the 404s that appear on refresh.
Key points
- An `@name` folder is a slot: it adds no URL segment.
- The layout receives slots as props, `children` included.
- Without `default.tsx`, a direct visit on that slot returns 404.
- A slot cannot be linked to: deep links go through the page or an intercepting route.
A parallel route is a named slot: `@analytics` inside `app/dashboard/` renders into the same page, with its own `loading.tsx` and `error.tsx`. It is for independent panels and for modals that need a URL.
The minimal structure
app/
dashboard/
layout.tsx
page.tsx
@analytics/
page.tsx
loading.tsx
default.tsx
@team/
page.tsx
default.tsx
The folder with the at sign adds no URL segment: `/dashboard` stays `/dashboard`. What changes is how many things the `layout` receives and can compose.
The layout receives slots as props
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
analytics,
team,
}: {
children: React.ReactNode;
analytics: React.ReactNode;
team: React.ReactNode;
}) {
return (
{children}
{analytics}
{team}
);
}
`children` is the implicit slot: the folder's `page.tsx`. The other slots take the folder name without the at sign. If the layout does not render a slot, that content does not appear and nothing warns you.
What they are actually for
- Dashboards whose panels load at different speeds: each slot gets its own waiting boundary.
- A modal with a shareable URL, by pairing the slot with an intercepting route.
- Tabs that keep their state when the reader switches section.
- Two views of the same page, one for the team and one for the client, without duplicating the layout.
For the active state there is `useSelectedLayoutSegment`, which takes the slot name. It only works in a client component: on the server the segment is not exposed as a hook.
// app/dashboard/@team/team-tabs.tsx
"use client";
import { useSelectedLayoutSegment } from "next/navigation";
export function TeamTabs() {
const segment = useSelectedLayoutSegment("team");
return {segment ?? "overview"};
}
The constraint that breaks deploys
Every slot needs a `default.tsx`. Without it, a direct visit to a URL that does not match the slot returns 404. Soft navigation hides the bug; it shows up on refresh or on the first visit from a link.
// app/dashboard/@analytics/default.tsx
export default function AnalyticsDefault() {
return null;
}
| File | Role | Required |
|---|---|---|
| `layout.tsx` | Receives the slots as props | Yes, to render them |
| `@slot/page.tsx` | Slot content | Yes, for that slot |
| `@slot/default.tsx` | Fallback on direct navigation | Yes, if the slot can go unmatched |
| `@slot/loading.tsx` | Independent waiting state | No, but it is the reason to use slots |
The number that matters
The EneUI catalogue indexes 111,985 components, 1,556 blocks and 12,808 icons from 1,472 libraries, measured on 1 November 2026 at `/api/stats`. Three categories cover the slots: tabs, dashboard blocks and modals.
The slot inherits its waiting boundaries: read streaming and Suspense in the App Router. For isolated failure there is error boundaries in the App Router. For the general picture, how to structure an App Router project. For a framework comparison, Astro vs Next.js.
Known limitation: a slot is not a route. You cannot link to `/dashboard/@analytics`, and you cannot nest one slot inside another beyond the layout level that declares them.
Frequently asked questions
Does a parallel route change the URL?
No. The at sign marks a slot, not a segment. `/dashboard` is the same URL with or without `@analytics`.
Why do I get a 404 on refresh?
The slot has no `default.tsx`. Soft navigation keeps the component mounted, but on refresh Next.js must fill the slot and finds no fallback.
Can I use slots for modals?
Yes, together with an intercepting route such as `(.)photo/[id]`. The slot renders the modal and the page underneath stays mounted.
Do slots share data fetching?
No. Each slot renders its own tree with its own fetches. If two slots read the same datum, React does not deduplicate across them.
How many slots can I have?
As many as you want in one layout, as long as the names are unique. Past three or four the layout becomes hard to read.