Parallel Routes with Named Slots and Default Segments
Render multiple independent route trees simultaneously using @slot conventions and default.tsx.
Parallel Routes with Named Slots and Default Segments is a free Next.js 15 Fullstack (App Router + Server Actions) lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Next.js 15 Fullstack (App Router + Server Actions) learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Are Parallel Routes?
Parallel Routes is a Next.js App Router feature that lets you render multiple independent route segments simultaneously inside a single layout. Each segment lives in its own named slot and can navigate, load, and error independently.
- Think of a dashboard with a sidebar, a main panel, and a notification feed — all loaded in parallel, each with its own loading/error states.
- Parallel routes are defined using the
@foldernaming convention inside a route segment directory. - Each slot becomes a prop on the parent
layout.tsx.
This is fundamentally different from nested layouts: parallel slots share the same URL but render separate route trees side by side.
The @slot Convention
To create a parallel slot, prefix a folder with @ inside any route segment. Next.js treats these as named slots rather than URL segments.
app/dashboard/@analytics/page.tsx→ slot namedanalyticsapp/dashboard/@team/page.tsx→ slot namedteam- The
@foldername does not appear in the URL — visiting/dashboardrenders all matching slots.
A typical parallel-routes directory tree looks like this:
// Directory layout (not runnable — filesystem structure)
// app/
// dashboard/
// layout.tsx <- receives @analytics and @team as props
// page.tsx <- default slot content (children)
// @analytics/
// page.tsx
// loading.tsx
// @team/
// page.tsx
// error.tsxWriting the Parallel Layout
The parent layout.tsx receives each named slot as a React prop alongside the standard children prop. You can position them anywhere in the JSX.
- TypeScript types for slots are just
React.ReactNode. - Slots are completely independent — one can suspend while another is already rendered.
- The
childrenprop itself is an implicit slot namedchildren.
// app/dashboard/layout.tsx
import React from 'react';
interface DashboardLayoutProps {
children: React.ReactNode;
analytics: React.ReactNode;
team: React.ReactNode;
}
export default function DashboardLayout({
children,
analytics,
team,
}: DashboardLayoutProps) {
return (
<div className="dashboard-grid">
<main className="col-span-2">{children}</main>
<aside className="analytics-panel">{analytics}</aside>
<aside className="team-panel">{team}</aside>
</div>
);
}Creating Slot Pages
Each named slot needs its own page.tsx. These pages are standard Server Components (or Client Components) — there is nothing special about their internal structure.
- Slots can have their own
loading.tsx,error.tsx, and nested routes. - Data fetching inside a slot is independent: one slot can
awaita slow database call without blocking the others.
// app/dashboard/@analytics/page.tsx
import { getAnalyticsSummary } from '@/lib/analytics';
export default async function AnalyticsSlot() {
const summary = await getAnalyticsSummary();
return (
<section>
<h2 className="text-lg font-semibold">Analytics</h2>
<p>Total visits: <strong>{summary.totalVisits}</strong></p>
<p>Conversion rate: <strong>{summary.conversionRate}%</strong></p>
</section>
);
}
// app/dashboard/@team/page.tsx
import { getTeamMembers } from '@/lib/team';
export default async function TeamSlot() {
const members = await getTeamMembers();
return (
<ul>
{members.map((m) => (
<li key={m.id}>{m.name} — {m.role}</li>
))}
</ul>
);
}Independent Loading States
Each slot can ship its own loading.tsx. Next.js wraps it in a Suspense boundary automatically, so slots stream in independently — fast slots appear immediately while slow ones show a skeleton.
- This eliminates the waterfall problem common in single-tree layouts.
- Users see meaningful UI much sooner because partial content renders first.
// app/dashboard/@analytics/loading.tsx
export default function AnalyticsLoading() {
return (
<div className="animate-pulse space-y-2">
<div className="h-4 bg-gray-200 rounded w-1/2" />
<div className="h-4 bg-gray-200 rounded w-3/4" />
<div className="h-4 bg-gray-200 rounded w-2/3" />
</div>
);
}
// app/dashboard/@team/loading.tsx
export default function TeamLoading() {
return (
<ul className="space-y-2">
{Array.from({ length: 4 }).map((_, i) => (
<li key={i} className="h-4 bg-gray-200 rounded animate-pulse" />
))}
</ul>
);
}The Problem: Soft Navigation and Missing Slots
Parallel routes shine during hard navigation (full page load), but soft navigation (client-side link clicks) reveals a problem.
When a user navigates to a sub-route — for example /dashboard/settings — Next.js looks for a matching page in every active slot. If a slot has no page for /settings, it becomes unmatched.
- An unmatched slot does not simply disappear — Next.js needs to know what to render.
- Without a fallback, Next.js renders a 404 for the entire page.
- The solution is
default.tsx.
Rescuing Unmatched Slots with default.tsx
default.tsx is the fallback file Next.js renders for a slot when it has no matching page.tsx for the current URL. It acts like a catch-all within the slot's subtree.
- Place
default.tsxat the root of each slot:@analytics/default.tsx. - During soft navigation to an unmatched sub-route, Next.js renders
default.tsxinstead of 404-ing. default.tsxreceives the same params as a page; you can use it to show the slot's normal content unchanged, or a meaningful placeholder.
// app/dashboard/@analytics/default.tsx
// Rendered when the current URL has no matching page in this slot.
import { getAnalyticsSummary } from '@/lib/analytics';
export default async function AnalyticsDefault() {
// Reuse the same data as the main analytics page so the slot
// stays visible during navigation to other sub-routes.
const summary = await getAnalyticsSummary();
return (
<section>
<h2 className="text-lg font-semibold">Analytics</h2>
<p>Total visits: <strong>{summary.totalVisits}</strong></p>
</section>
);
}default.tsx for the Root children Slot
The implicit children slot (i.e. page.tsx at the segment root) also needs a default.tsx when parallel slots are present. Without it, navigating to a named slot's sub-route that doesn't match children will 404.
- Add
app/dashboard/default.tsxalongsideapp/dashboard/page.tsx. - It typically re-renders the same content as
page.tsx, or a neutral empty state.
// app/dashboard/default.tsx
// Fallback for the implicit 'children' slot during soft navigation.
export default function DashboardDefault() {
return (
<div>
<h1 className="text-2xl font-bold">Dashboard</h1>
<p className="text-gray-500">Select an item to get started.</p>
</div>
);
}Nested Routes Inside Slots
Named slots support their own nested route hierarchies. You can add [id] dynamic segments, route groups, or additional layouts entirely inside a slot — all without affecting the URL segments of sibling slots.
- Example:
@analytics/chart/[chartId]/page.tsxrenders at/dashboard/chart/42only inside the analytics slot. - The parent layout and other slots remain untouched.
- Each nested level inside a slot can have its own
loading.tsx,error.tsx, anddefault.tsx.
// app/dashboard/@analytics/chart/[chartId]/page.tsx
interface Props {
params: Promise<{ chartId: string }>;
}
export default async function ChartDetailPage({ params }: Props) {
const { chartId } = await params;
// In a real app, fetch chart data here
return (
<div>
<h3>Chart #{chartId}</h3>
<p>Detailed breakdown for chart {chartId}.</p>
</div>
);
}
// app/dashboard/@analytics/chart/[chartId]/default.tsx
export default function ChartDefault() {
return <p className="text-sm text-gray-400">No chart selected.</p>;
}Practical Pattern: Modal Alongside Page
A classic use-case for parallel routes is rendering a modal in one slot while the main content stays visible in another — without losing the current scroll position or unmounting components.
- Create an
@modalslot with adefault.tsxthat returnsnull(nothing visible by default). - When a user clicks a trigger, Next.js navigates to the modal's page, filling the slot.
- The main
childrenslot continues rendering unchanged.
// app/dashboard/@modal/default.tsx
// Renders nothing when no modal route is matched.
export default function ModalDefault() {
return null;
}
// app/dashboard/@modal/user/[id]/page.tsx
interface Props {
params: Promise<{ id: string }>;
}
export default async function UserModal({ params }: Props) {
const { id } = await params;
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center">
<div className="bg-white rounded-lg p-6 w-96">
<h2 className="text-xl font-bold">User #{id}</h2>
<p>Profile details would appear here.</p>
</div>
</div>
);
}TypeScript Tip: Strongly-Typed Slot Props
As the number of slots grows, keep your layout props explicit with a dedicated TypeScript interface. This makes refactoring safer and IDE autocompletion more helpful.
- Slot props are always
React.ReactNode— they accept any renderable value Next.js injects. - You can combine parallel slots with
searchParamsor routeparamsin the same layout signature. - For shared slot configuration (e.g. theming), pass context via a React Context Provider inside the layout rather than prop-drilling through each slot page.
// app/dashboard/layout.tsx — full typed version
import React from 'react';
import { DashboardNav } from '@/components/DashboardNav';
interface DashboardLayoutProps {
children: React.ReactNode; // implicit slot
analytics: React.ReactNode; // @analytics slot
team: React.ReactNode; // @team slot
modal: React.ReactNode; // @modal slot
params: Promise<{ orgId: string }>;
}
export default async function DashboardLayout({
children,
analytics,
team,
modal,
params,
}: DashboardLayoutProps) {
const { orgId } = await params;
return (
<>
<DashboardNav orgId={orgId} />
<div className="grid grid-cols-3 gap-4 p-4">
<div className="col-span-2">{children}</div>
<div className="space-y-4">
{analytics}
{team}
</div>
</div>
{modal}
</>
);
}Knowledge Check: When Is default.tsx Required?
Test your understanding of the default.tsx file in parallel routes.
Recap: Parallel Routes with Named Slots and Default Segments
Here is what you learned in this lesson:
- Parallel Routes let you render multiple independent route trees in the same layout simultaneously, each in its own
@slotfolder. - Named slots use the
@folderNameconvention and become props (React.ReactNode) on the parentlayout.tsx. - Each slot is truly independent: it can have its own
loading.tsx,error.tsx, nested routes, and data fetching without blocking siblings. default.tsxis the critical fallback file. Without it, soft navigation to a URL that does not match a slot produces a 404. Place adefault.tsxin every slot — including the implicitchildrenslot — to prevent this.- Common patterns include dashboard multi-panels (analytics + team side by side) and modal-alongside-page where the
@modalslot'sdefault.tsxreturnsnull. - Keep layout props strongly typed with a TypeScript interface as the slot count grows.
With parallel routes and default.tsx in place, your layouts become resilient, independently streaming, and far easier to reason about than a single monolithic route tree.
Frequently asked questions
Is the “Parallel Routes with Named Slots and Default Segments” lesson free?
Yes — the full text of “Parallel Routes with Named Slots and Default Segments” is free to read here on the web, and the Next.js 15 Fullstack (App Router + Server Actions) course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Next.js 15 Fullstack (App Router + Server Actions) course, upgrade to CoddyKit PRO.
What will I learn in “Parallel Routes with Named Slots and Default Segments”?
Render multiple independent route trees simultaneously using @slot conventions and default.tsx. You practise Next.js 15 Fullstack (App Router + Server Actions) with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Next.js 15 Fullstack (App Router + Server Actions)?
No prior experience is required. Next.js 15 Fullstack (App Router + Server Actions) on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Parallel Routes with Named Slots and Default Segments” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Next.js 15 Fullstack (App Router + Server Actions) lesson?
Yes. Every Next.js 15 Fullstack (App Router + Server Actions) lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Parallel Routes with Named Slots and Default Segments
- Intercepting Routes for Shared Modal Experiences
- Conditional Slot Rendering for Dashboards and Tabs
- Building a Photo-Modal Flow with Soft Navigation