Next.js Parallel Routes with the @folder Convention
Learn how to render multiple pages simultaneously in the same layout using Next.js parallel routes and the @folder naming convention.
What you'll learn
- ✓What parallel routes are and when to use them
- ✓How the @folder convention creates named slots
- ✓Rendering multiple page segments inside a single layout
- ✓Handling unmatched routes with default.tsx
- ✓Conditional rendering based on authentication state
Prerequisites
- •Working knowledge of the [Next.js App Router](/blog/nextjs-app-router-basics)
- •Familiarity with [layouts and nested routing](/blog/nextjs-dynamic-routes-and-catchall)
Parallel routes let you render more than one page component inside the same layout at the same time. Instead of replacing the entire view when a user navigates, you can independently load and display separate sections of the UI. Think dashboards with a main panel and a sidebar that each have their own navigation, or a feed page that also renders a modal without unmounting the feed.
The @folder convention
Next.js uses folder names that start with @ to define named slots. Each slot acts as a separate route segment that gets passed to the parent layout as a prop.
app/
layout.tsx
page.tsx
@analytics/
page.tsx
@feed/
page.tsx
The parent layout.tsx receives every slot as a prop alongside children:
// app/layout.tsx
export default function DashboardLayout({
children,
analytics,
feed,
}: {
children: React.ReactNode;
analytics: React.ReactNode;
feed: React.ReactNode;
}) {
return (
<div className="grid grid-cols-3 gap-4">
<main className="col-span-2">{children}</main>
<aside>
{analytics}
{feed}
</aside>
</div>
);
}
The children prop maps to the implicit slot for the page.tsx that sits directly inside app/. The analytics and feed props correspond to the @analytics and @feed folders.
Independent loading states
Because each slot is a separate route segment, you can add a loading.tsx file inside any @ folder to show a per-slot skeleton:
app/
@analytics/
loading.tsx
page.tsx
@feed/
loading.tsx
page.tsx
// app/@analytics/loading.tsx
export default function AnalyticsLoading() {
return <div className="animate-pulse h-48 bg-gray-200 rounded" />;
}
When the page first loads, each slot streams in independently. If the analytics query takes three seconds but the feed resolves in 200 milliseconds, the user sees the feed immediately while the analytics panel shows a skeleton. This is one of the biggest advantages over a single-page approach where the slowest data fetch blocks the entire render.
Handling unmatched routes with default.tsx
When a user navigates to a route that exists in one slot but not another, Next.js needs to know what to render for the missing slot. Without explicit handling, you get a 404. The fix is a default.tsx file:
// app/@analytics/default.tsx
export default function AnalyticsDefault() {
return <p>Select a date range to view analytics.</p>;
}
The default.tsx acts as a fallback. It renders when the current URL does not match any page inside that slot. You will almost always want a default.tsx in every parallel route folder to avoid unexpected 404 pages during soft navigation.
Nested parallel routes
Slots can have their own nested routes. This lets each slot respond to different URL segments independently:
app/
layout.tsx
page.tsx
@team/
page.tsx
[memberId]/
page.tsx
@activity/
page.tsx
[memberId]/
page.tsx
default.tsx
Navigating to /team/42 renders the @team/[memberId]/page.tsx slot and the @activity/[memberId]/page.tsx slot in parallel, each receiving { params: { memberId: '42' } }.
Conditional slots based on auth
A powerful pattern is choosing which slot to render based on the user’s authentication state. Because the layout receives slots as props, you can conditionally display them:
// app/layout.tsx
import { getSession } from '@/lib/auth';
export default async function RootLayout({
children,
dashboard,
login,
}: {
children: React.ReactNode;
dashboard: React.ReactNode;
login: React.ReactNode;
}) {
const session = await getSession();
return (
<html lang="en">
<body>
{session ? dashboard : login}
{children}
</body>
</html>
);
}
app/
layout.tsx
page.tsx
@dashboard/
page.tsx
default.tsx
@login/
page.tsx
default.tsx
This avoids redirects. Both the dashboard and login components are rendered on the server, but only the relevant one is sent to the client based on the session check.
Combining parallel routes with intercepting routes
Parallel routes pair naturally with intercepting routes to build modal patterns. You can use a parallel slot as the modal container and an intercepting route to capture navigation without a full page transition:
app/
layout.tsx
@modal/
(.)photo/[id]/
page.tsx
default.tsx
feed/
page.tsx
photo/[id]/
page.tsx
The layout renders the @modal slot on top of children. When a user clicks a photo link from the feed, the intercepting route (.)photo/[id] renders inside the modal slot while the feed stays visible underneath. A hard refresh or direct URL visit skips the intercept and renders the full photo/[id]/page.tsx instead.
Error handling per slot
Each slot can have its own error.tsx boundary:
// app/@analytics/error.tsx
'use client';
export default function AnalyticsError({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div className="p-4 bg-red-50 rounded">
<p>Failed to load analytics: {error.message}</p>
<button onClick={reset} className="mt-2 text-blue-600 underline">
Retry
</button>
</div>
);
}
If the analytics slot throws, only that slot shows the error UI. The feed and the main content remain fully interactive.
Common pitfalls
Forgetting default.tsx is the most frequent issue. Without it, client-side navigation to a route that does not match a slot will render a 404 for that slot, which can break the entire layout.
Slots are not URL segments. The @analytics folder does not add /analytics to the URL. It is purely an organizational convention that tells the router to inject the rendered component as a prop.
Hard vs. soft navigation matters. On a soft navigation (clicking a <Link>), Next.js preserves the previously active state of unmatched slots. On a hard navigation (full page load), unmatched slots render their default.tsx. Design your defaults accordingly.
When to use parallel routes
Parallel routes shine in these scenarios:
- Dashboards where multiple panels update independently
- Split views like an email client with a list and a detail pane
- Modals that overlay content without unmounting the underlying page
- Conditional layouts that swap sections based on user role or feature flags
For simpler pages where only one main content area changes at a time, standard nested routes are sufficient. Parallel routes add complexity, so reach for them when you genuinely need independent, simultaneously rendered page segments.
Related articles
- Next.js Next.js Intercepting Routes for Modals
Build modal patterns in Next.js using intercepting routes. Learn the (.) convention, combining with parallel routes, and handling hard refreshes.
- Next.js Next.js API Routes: Best Practices and Patterns
Build robust Next.js API routes with proper validation, error handling, authentication, rate limiting, and testing patterns for production apps.
- Next.js Next.js i18n: Complete Internationalization Guide
Build multilingual Next.js apps with the App Router using locale routing, server-side translations, dynamic language switching, and SEO best practices.
- Next.js Next.js Server Actions: Forms and Mutations Guide
Learn how to use Next.js Server Actions for form handling, data mutations, optimistic updates, and error handling with practical examples.