Made in Builder.io

Join us for the biggest Figma-to-code launch of the year

Builder.io logo
Talk to Us
Platform
Developers
Talk to Us

Blog

Home

Resources

Blog

Forum

Github

Login

Signup

×

Visual CMS

Drag-and-drop visual editor and headless CMS for any tech stack

Theme Studio for Shopify

Build and optimize your Shopify-hosted storefront, no coding required

Resources

Blog

Get StartedLogin

‹ Back to blog

Web Development

Next.js 14 Parallel Routes: A Detailed Guide

December 20, 2023

Written By Vishwas Gopinath

The App Router in Next.js 14 provides a set of conventions to help you implement advanced routing patterns, one of which is parallel routes. In this blog post, we will learn what parallel routes are, how to define them, and the benefits they offer when creating dynamic and complex user interfaces.

What are parallel routes?

Parallel routes are an advanced routing mechanism that allows for the simultaneous rendering of multiple pages within the same layout. Let's explore this concept with a practical example.

Consider the challenge of building a complex dashboard for a web application. Such a dashboard might need to display various views like user analytics, revenue metrics, and notifications, all at once.

Complex dashboard

Traditionally, we would create components for each section and arrange them in the layout.tsx file within an app/dashboard folder. The code might look like this:

// app/dashboard/layout.tsx

import UserAnalytics from "@/components/UserAnalytics";
import RevenueMetrics from "@/components/RevenueMetrics";
import Notifications from "@/components/Notifications";

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <>
      <div>{children}</div> {/* Content from page.tsx */}
      <UserAnalytics /> {/* Component for user analytics */}
      <RevenueMetrics /> {/* Component for revenue metrics */}
      <Notifications /> {/* Component for notifications */}
    </>
  );
}

In this setup, the layout automatically receives the default component exported from app/dashboard/page.tsx as the children prop, which is then rendered within the layout. For simplicity, let's assume page.tsx renders just a title “Dashboard”. Each of the other components represents a specific section of the dashboard, such as a Card with the necessary data rendered inside.

While this traditional approach of component composition is effective, using parallel routes can achieve the same outcome with additional benefits. So, how do we define parallel routes in Next.js?

Parallel routes in Next.js are defined using a feature known as slots. Slots help structure our content in a modular fashion. To define a slot, we use the @folder naming convention. Each slot is then passed as a prop to its corresponding layout.tsx file .

In the context of our dashboard example, we would define three distinct slots within the dashboard folder: @users for the user analytics section, @revenue for the revenue metrics section, and @notifications for the notifications section:

Parallel Routes
// app/dashboard/@notifications/page.tsx
export default function Notifications() {
  return <div>Notifications</div>;
}

// app/dashboard/@revenue/page.tsx
export default function RevenueMetrics() {
  return <div>Revenue Metrics</div>;
}

// app/dashboard/@users/page.tsx
export default function UsersAnalytics() {
  return <div>User Analytics</div>;
}

Find the source code, including inline styles, in my GitHub repo.

Each slot is automatically passed to the layout as a prop, which we can use to structure the dashboard page. For simplicity, styling is omitted from the JSX.

// slots are available as props in the layout

export default function DashboardLayout({
  children,
  users,
  revenue,
  notifications,
}: {
  children: React.ReactNode,
  users: React.ReactNode,
  revenue: React.ReactNode,
  notifications: React.ReactNode,
}) {
  return (
    <div>
      <div>{children}</div>
      <div>{users}</div>
      <div>{revenue}</div>
      <div>{notifications}</div>
    </div>
  );
}

You can see that the slots are available as props, and we don’t have to import them. When navigating to localhost:3000/dashboard, the UI remains the same as before.

Dashboard UI

It is important to note that slots are not route segments and do not affect the URL structure. What you should also know is that the children prop is an implicit slot that does not need to be mapped to a folder, which is why dashboard/page.tsx is equivalent to dashboard/@children/page.tsx.

But what is the benefit of building our user interface with parallel routes?

A clear benefit of parallel routes is their ability to split a single layout into various slots, making the code more manageable. This is particularly advantageous when different teams work on various sections of the page.

However, the true benefit of parallel routes lies in their capacity for independent route handling and sub-navigation. Let’s take a closer look at these benefits.

One of the most compelling features of parallel routes is the ability to handle each route independently. This means that each slot of your layout, such as user analytics or activity logs, can have its own loading and error states. This granular control is particularly beneficial in scenarios where different sections of the page load at varying speeds or encounter unique errors.

Loading and error slots

For instance, if the user analytics data takes longer to load, you can display a loading spinner specifically for that section, while other parts of the dashboard remain interactive. Similarly, if there's an error in fetching revenue metrics, you can show an error message in that specific section without affecting the rest of the dashboard. This level of detail in handling states not only improves the user experience but also simplifies debugging and maintenance.

Another significant advantage of using parallel routes is their capability to offer a seamless sub-navigation experience within each parallel route. Each slot of your dashboard can essentially function as a mini-application, complete with its own navigation and state management. This is especially useful in a complex application such as our dashboard where different sections serve distinct purposes.

Imagine each section of your dashboard – be it user analytics, revenue metrics, or notifications – operating as a standalone entity. Users can interact with each section independently, applying filters, sorting data, or navigating through pages, without affecting the state or display of other sections.

For instance, in the notifications section, users can switch to an "archived" view from the default view. This interaction allows them to view past notifications that are no longer in the main feed. The ability to toggle between the default and archived views, and the interactions within each, are confined to the notifications section alone. The URL in the address bar updates to reflect the current view, ensuring that the link is always shareable helps users know where they are on the site.

This approach allows for a more dynamic and interactive user experience, as users can navigate through different parts of the application without unnecessary page reloads or layout shifts.

Working with Parallel Routes and Sub-Navigation

When implementing parallel routes and sub-navigation, it's essential to understand a key concept. To understand what that is, we will focus on an implementation scenario involving archived notifications within our dashboard.

Sub navigation

In this example, we will add sub-navigation to the notifications section of our dashboard. This will involve creating a link within the notifications slot to access the archived notifications page and a reciprocal link in the archived notifications page to return to the default page.

  1. Navigating to archived notifications: In dashboard/@notifications/page.tsx, include a link to the archived notifications:
  2. Setting up the archived notifications route: Inside the @notifications folder, create an archived subfolder. Define a page here that features a link back to the /dashboard route, which displays the default notifications view.

This setup allows seamless navigation between the default and archived notification views, without impacting other sections of the dashboard.

However, it's important to consider how this affects other parts of the dashboard, particularly the behavior of the children, users, and revenue slots since they don't have an archived folder defined.

By default, the content rendered within a slot matches the current URL. In our dashboard folder, we have four slots: children, users, revenue and notifications. All these slots render their defined content when visiting localhost:3000/dashboard. However, when navigating to localhost:3000/dashboard/archived, only the notifications slot has a matching route. The other three slots - children, users, and revenue - become unmatched.

When dealing with an unmatched slot, the content rendered by Next.js depends on the routing approach:

  1. Navigation from the UI: In the case of navigation within the UI, Next.js retains the previously active state of a slot regardless of changes in the URL. This means when you navigate between the default notifications at /dashboard and archived notifications at /dashboard/archived within the notifications slot, the other slots – children, users, and revenue – remain unaffected. These slots continue to display whatever content they were showing before, and are not influenced by the shift in the URL path from /dashboard to /dashboard/archived or the reverse.
  2. Page Reloads: In the case of a page reload, Next.js immediately searches for a default.tsx file within each unmatched slot. The presence of this file is critical, as it provides the default content that Next.js will render in the user interface. If this default.tsx file is missing in any of the unmatched slots for the current route, Next.js will render a 404 error. For example, reloading the page after navigating to /dashboard/archived results in a 404 error because there is no default.tsx file in the children, users, or revenue slots. Without this file, Next.js cannot determine the default content for these slots on the initial load.

Let’s take a closer look at including the default.tsx file in our dashboard route.

The default.tsx file in Next.js serves as a fallback to render content when the framework cannot retrieve a slot's active state from the current URL. You have complete freedom to define the UI for unmatched routes: you can either mirror the content found in page.tsx or craft an entirely custom view

In our scenario, to prevent 404 errors when rendering the /dashboard/archived route, we will include default.tsx files. For simplicity, the content from page.tsx will be replicated in default.tsx. This means that for each slot, default.tsx will display the same user interface as its corresponding page.tsx.

Our dashboard layout comprises four slots:

<div>
  <div>{children}</div>
  <div>{users}</div>
  <div>{revenue}</div>
  <div>{notifications}</div>
</div>

In this layout, only the @notifications slot has a designated component for the /archived route. To prevent 404 errors for the other sections when accessing this route, we need to set up default views. These default views should be at the same level in the directory structure as page.tsx. Here's how we'll do it:

// app/dashboard/default.tsx
export default function DefaultDashboardPage() {
  return <div>Dashboard</div>;
}

// app/dashboard/@revenue/default.tsx
export default function DefaultRevenueMetrics() {
  return <div>Revenue Metrics</div>;
}

// app/dashboard/@users/default.tsx
export default function DefaultUsersAnalytics() {
  return <div>User Analytics</div>;
}
  1. children slot: Define default.tsx within the dashboard folder. This will serve as the fallback view for the ‘children’ slot.
  2. users slot: Define default.tsx inside the @users folder. This will serve as the fallback view for the ‘users’ slot.
  3. revenue slot: Define default.tsx in the @revenue folder. This will serve as the fallback view for the ‘revenue’ slot.

With the default.tsx files set up as described, if you reload localhost:3000/dashboard/archived, the page will load correctly instead of showing a 404 error. Here's what happens:

  • The 'notifications' slot will show its specific content from the archived subfolder, as it's the only slot with a component defined for the /archived route.
  • The other three slots - 'children', 'users', and 'revenue' - will display the content from their respective default.tsx files. These files act as fallbacks for routes without specific content.

This approach ensures that you don't accidentally render a route that shouldn't be parallel rendered.

Before we conclude, let's briefly discuss conditional routes. Parallel Routes offer a way to implement conditional routing. For instance, based on the user's authentication state, you can choose to render the dashboard for authenticated users or a login page for those who are not authenticated.

import { getUser } from "@/lib/auth";

export default function Layout({
  children,
  users,
  revenue,
  notifications,
  login,
}: {
  children: React.ReactNode;
  users: React.ReactNode;
  revenue: React.ReactNode;
  notifications: React.ReactNode;
  login: React.ReactNode;
}) {
  const isLoggedIn = getUser();
  return isLoggedIn ? (
    <>
      {children}
      {users}
      {revenue}
      {notifications}
    </>
  ) : (
    login
  );
}

If the login route fails to render, please restart the development server.

Next.js 14's parallel routes offer a powerful way to build dynamic and complex user interfaces. This approach not only simplifies code management but also enhances user experience with independent route handling and sub-navigation.

Key Takeaways:

  • Parallel routes allow simultaneous rendering of different pages within the same layout.
  • Parallel routes are defined using slots.
  • Slots organize content in a modular fashion, making code more manageable.
  • The use of default.tsx for unmatched routes ensure a consistent user experience, even when certain content sections don't have a direct match in the URL.
  • Parallel Routes can be used to implement conditional routing

Share

Twitter
LinkedIn
Facebook
Hand written text that says "A drag and drop headless CMS?"

Introducing Visual Copilot:

A new AI model to turn Figma designs to high quality code.

Try Visual Copilot
Newsletter

Like our content?

Join Our Newsletter

Continue Reading
Web Development20 MIN
Why React Server Components Are Breaking Builds to Win Tomorrow
WRITTEN BYVishwas Gopinath
February 28, 2024
Web Development20 MIN
The definitive guide to building a drag and drop editable blog with Builder
WRITTEN BYTim Garibaldi
February 26, 2024
Qwik20 MIN
Towards Qwik 2.0: Lighter, Faster, Better
WRITTEN BYThe Qwik Team
February 9, 2024