0Pricing
Next.js 15 Fullstack (App Router + Server Actions) · Lesson

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 @folder naming 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 named analytics
  • app/dashboard/@team/page.tsx → slot named team
  • The @folder name does not appear in the URL — visiting /dashboard renders 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.tsx

Writing 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 children prop itself is an implicit slot named children.
// 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 await a 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.tsx at the root of each slot: @analytics/default.tsx.
  • During soft navigation to an unmatched sub-route, Next.js renders default.tsx instead of 404-ing.
  • default.tsx receives 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.tsx alongside app/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.tsx renders at /dashboard/chart/42 only 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, and default.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 @modal slot with a default.tsx that returns null (nothing visible by default).
  • When a user clicks a trigger, Next.js navigates to the modal's page, filling the slot.
  • The main children slot 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 searchParams or route params in 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 @slot folder.
  • Named slots use the @folderName convention and become props (React.ReactNode) on the parent layout.tsx.
  • Each slot is truly independent: it can have its own loading.tsx, error.tsx, nested routes, and data fetching without blocking siblings.
  • default.tsx is the critical fallback file. Without it, soft navigation to a URL that does not match a slot produces a 404. Place a default.tsx in every slot — including the implicit children slot — to prevent this.
  • Common patterns include dashboard multi-panels (analytics + team side by side) and modal-alongside-page where the @modal slot's default.tsx returns null.
  • 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

  1. Parallel Routes with Named Slots and Default Segments
  2. Intercepting Routes for Shared Modal Experiences
  3. Conditional Slot Rendering for Dashboards and Tabs
  4. Building a Photo-Modal Flow with Soft Navigation
← Back to Next.js 15 Fullstack (App Router + Server Actions)