| name | implementing-parallel-routes |
| description | Teach parallel routes and slot patterns in Next.js 16. Use when implementing parallel routes, working with @slot syntax, or encountering missing default.tsx errors. |
| allowed-tools | Read, Write, Edit, Glob, TodoWrite |
| version | 1.0.0 |
Parallel Routes in Next.js 16
Concept
Parallel routes allow you to simultaneously render multiple pages within the same layout. Each route is defined in a "slot" using the @folder naming convention.
Key characteristics:
- Slots are defined with
@prefix (e.g.,@team,@analytics) - Each slot is passed as a prop to the parent layout
- Slots render independently and can have their own loading/error states
- Navigation within one slot doesn't affect other slots
Basic Structure
app/
├── @team/
│ ├── page.tsx
│ └── default.tsx
├── @analytics/
│ ├── page.tsx
│ └── default.tsx
├── layout.tsx
└── page.tsx
The layout receives slots as props:
export default function Layout({
children,
team,
analytics,
}: {
children: React.ReactNode
team: React.ReactNode
analytics: React.ReactNode
}) {
return (
<div>
<div>{children}</div>
<div className="grid grid-cols-2 gap-4">
<div>{team}</div>
<div>{analytics}</div>
</div>
</div>
)
}
default.tsx Requirement
CRITICAL: Every slot MUST have a default.tsx file.
When navigating to a route that doesn't match the current slot, Next.js will render the default.tsx fallback. Without it, you'll get a 404 error.
export default function Default() {
return null
}
Common error:
Error: The default export is not a React Component in route: /@team
Solution: Add default.tsx to every @slot directory.
Navigation Behavior
Parallel routes handle navigation differently than normal routes:
Soft Navigation
When navigating using <Link> or router.push():
- Active slots maintain their current state
- Only the children segment updates
- Slots remain "mounted"
Hard Navigation
When navigating via browser refresh or direct URL entry:
- All slots reset to their default state
default.tsxfiles render for unmatched routes- Each slot independently resolves its route
Example scenario:
app/
├── @modal/
│ ├── login/
│ │ └── page.tsx
│ └── default.tsx
├── layout.tsx
└── page.tsx
- User visits
/→@modalrendersdefault.tsx - User clicks link to
/login→@modalrenderslogin/page.tsx - User refreshes page → URL changes back to
/,@modalrendersdefault.tsx
This is why intercepting routes typically use parallel routes for modals.
Slot Matching
Slots match based on the current URL segment:
app/
├── dashboard/
│ ├── @sidebar/
│ │ ├── settings/
│ │ │ └── page.tsx
│ │ └── default.tsx
│ ├── settings/
│ │ └── page.tsx
│ └── layout.tsx
└── page.tsx
At /dashboard/settings:
dashboard/settings/page.tsxrenders inchildrendashboard/@sidebar/settings/page.tsxrenders insidebarslot- If
@sidebar/settings/page.tsxdoesn't exist,@sidebar/default.tsxrenders
Conditional Rendering
You can conditionally render slots:
export default function Layout({
children,
modal,
auth,
}: {
children: React.ReactNode
modal: React.ReactNode
auth: React.ReactNode
}) {
const session = await getSession()
return (
<div>
{children}
{modal}
{!session && auth}
</div>
)
}
Common Gotchas
1. Missing default.tsx
Problem: 404 errors when navigating
Solution: Add default.tsx to every slot directory
2. Slot not rendering
Problem: Slot prop is undefined in layout
Solution: Check slot name matches @folder name (case-sensitive)
3. Unexpected resets
Problem: Slot resets to default on navigation
Solution: Ensure target route exists in slot, or use default.tsx intentionally
4. Nested layouts
Problem: Slots not accessible in nested layouts Solution: Slots only pass to immediate parent layout, not descendants
5. Route groups with slots
Problem: Confusion about where to place @slot folders
Solution: Place slots at the same level as the layout that consumes them
app/
├── (marketing)/
│ ├── @hero/
│ │ └── page.tsx
│ ├── layout.tsx
│ └── page.tsx
Practical Example: Modal Pattern
app/
├── @modal/
│ ├── (.)photo/
│ │ └── [id]/
│ │ └── page.tsx
│ └── default.tsx
├── photo/
│ └── [id]/
│ └── page.tsx
├── layout.tsx
└── page.tsx
Layout with modal slot:
export default function Layout({
children,
modal,
}: {
children: React.ReactNode
modal: React.ReactNode
}) {
return (
<>
{children}
{modal}
</>
)
}
Modal slot default:
export default function Default() {
return null
}
Intercepted route renders in modal:
export default function PhotoModal({ params }: { params: { id: string } }) {
return (
<dialog open>
<img src={`/photos/${params.id}.jpg`} />
</dialog>
)
}
Direct route renders full page:
export default function PhotoPage({ params }: { params: { id: string } }) {
return (
<main>
<img src={`/photos/${params.id}.jpg`} />
</main>
)
}
When to Use Parallel Routes
Good use cases:
- Dashboard layouts with independent panels
- Modal/drawer patterns with intercepting routes
- Split views with independent navigation
- A/B testing different UI sections
- Conditional sidebar/navigation rendering
Avoid when:
- Simple component composition suffices
- You need parent-child data flow
- Navigation should be tightly coupled
- You're just trying to organize files (use route groups instead)
References
- Next.js Docs: https://nextjs.org/docs/app/building-your-application/routing/parallel-routes
- Intercepting Routes: nextjs-16 skill
ROUTING-intercepting-routes - Route Groups: nextjs-16 skill
ROUTING-route-groups