Parallel Routes In Next.js
Published: 2024-04-17 11:04:35

by [Praise Babatunde](https://blog.openreplay.com/authors/praise-babatunde)





<blockquote><em>
Parallel routes in Next.js are a powerful new way to structure your application's layout and content, allowing you to render multiple independent pages within the same layout simultaneously. With an example, this tutorial will look at parallel routes and their benefits.
</em></blockquote>


<div style="background-color:#efefef; border-radius:8px; padding:10px; display:block;">
<hr/>
<h3><em>Session Replay for Developers</em></h3> 
<p><em>Uncover frustrations, understand bugs and fix slowdowns like never before with <strong><a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a></strong> — an open-source session replay suite for developers. It can be <strong>self-hosted</strong> in minutes, giving you complete control over your customer data.</em></p>
<img alt="OpenReplay" style="margin-top:5px; margin-bottom:5px;" width="768" height="400" src="https://raw.githubusercontent.com/openreplay/openreplay/main/static/openreplay-git-hero.svg" class="astro-UXNKDZ4E" loading="lazy" decoding="async">
<p><em>Happy debugging! <a href="https://openreplay.com" target="_blank">Try using OpenReplay today.</a></em><p>
<hr/>
</div>



[Parallel routes](https://nextjs.org/docs/app/building-your-application/routing/parallel-routes) in Next.js significantly advance the framework's routing capabilities. They allow developers to render multiple pages within the same layout simultaneously or conditionally, which is particularly beneficial for dynamic sections of applications like dashboards and social media feeds.

Traditionally, routing in Next.js works by defining a single route for each URL pattern. When a user navigates to a URL, Next.js renders the corresponding route and replaces the current page with the new one. However, this can lead to slow loading times and a less responsive user experience, especially when dealing with complex applications.

Parallel routes aim to solve this problem by allowing developers to define multiple routes that can be rendered together without the need to navigate between separate pages. This means that when a user clicks on a link or enters a URL, Next.js can render multiple routes simultaneously, displaying the content from each route side-by-side.
## Defining Parallel Routes
To implement parallel routes in Next.js, developers use a feature called slots. Slots are defined using the `@folder` naming convention and designed to structure content modularly. Each slot is passed as a prop to a corresponding `layout.tsx` file. For instance, you might define slots for users, the team, and notifications in a dashboard application.
![dashboard created using parallel routes](https://blog.openreplay.com/images/parallel-routes-in-nextjs/images/image1.png)
Each slot can be independently rendered within the layout, allowing for a dynamic and complex user interface.
## How to use Parallel Routes
Let's dive into the code to see this in action. 

So, we want to create a simple demonstration app with a dashboard that displays content views like the `users` page, `notifications` page, and `team` page using parallel routes.

If we want to do this traditionally using component composition, it'll be like this:
```javascript
// app/dashboard/layout.tsx
import User from "@/components/users";
import Team from "@/components/team";
import Notification from "@/components/notifications";

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <>
      <div>{children}</div> {/* Content from page.tsx */}
      <User /> {/* Component for user */}
      <Team /> {/* Component for Team */}
      <Notification /> {/* Component for notification */}
    </>
  );
}
```
While this traditional approach of component composition is effective, using parallel routes can achieve the same outcome with additional benefits.

Open up the terminal and create a new project. Change the directory to it and install Next.js using the following command:
```bash
npx create-next-app@latest
```
Give the project a name and follow the instructions to set up the app as you like. I'll be using typescript with a bit of tailwind for this tutorial.

Once that is done, start up the development server using the following code:
```bash
npm run dev
```
Now that our development server is up and running, let's showcase how parallel routes work.

The first thing to do is create the dashboard page. Navigate to  `app/page.tsx`, delete, and paste the following code:
```javascript
export default async function Dashboard() {
  return (
    <div>
      <h1 className="text-3xl font-bold">Dashboard</h1>
    </div>
  );
}
```
Here, we created a simple dashboard component.

We'll need to define the slots, which are the `users,` `teams,` and notifications using the `@folder` naming convention. These slots will act as the contents rendered or displayed inside our dashboard. To do this, we will define parallel routes for them.

Inside the `app` folder, create a folder called `@users,` and inside it, create a file called `page.tsx`. Add the following code:
```javascript
export default async function Users() {
  return (
    <div className="h-60 rounded-xl bg-yellow-800 p-10 text-white">
      <h1 className="text-3xl font-bold">Users</h1>
    </div>
  );
}
```
In the code above, we created a `users` component. We'll also do that for the other two slots.

Create a folder called `@team,` and a `page.tsx` file inside it. Add the following code:
```javascript
export default async function Team() {
  return (
    <div className="h-96 flex-1 rounded-2xl bg-green-800 p-10 text-white">
      <h2 className="text-xl font-semibold">Team</h2>
    </div>
  );
}
```
Lastly, for the slots, inside the `app` folder, create a folder called `@notifications,` and paste this:
```javascript
export default async function Dashboard() {
  return (
    <div className="h-96 flex-1 rounded-2xl bg-purple-800 p-10 text-white">
      <h2 className="text-xl font-semibold">Dashboard</h2>
    </div>
  );
}
```
Now that we have our slots defined, the next step is to access the three slots we defined as attributes of the props object and render them dynamically. Navigate to the `app/layout.tsx` file and change the code to this:
```javascript
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
  team,
  notifications,
  users,
}: {
  children: React.ReactNode;
  team: React.ReactNode;
  notifications: React.ReactNode;
  users: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <main className="container">
          <section className="py-6">{children}</section>

          <section className="flex flex-col h-full gap-6 ms-12">
            {/* User component occupies the full width */}
            <div className="flex gap-6">
              <div className="w-full">{users}</div>
            </div>

            {/* Team and Notification components are stacked below */}
            <div className="flex gap-6">
              <div className="w-full">{team}</div>
              <div className="w-full">{notifications}</div>
            </div>
          </section>
        </main>
      </body>
    </html>
  );
}
```
Each slot here is passed automatically to the layout as a prop, which allows us to create the dashboard. 
 
Checking the result in the browser, we should have this:
![Dashboard using parallel_routes](https://blog.openreplay.com/images/parallel-routes-in-nextjs/images/image2.gif)
You can see that the dashboard has been successfully rendered using parallel routes.

Since we can achieve this traditionally, why use parallel routes anyway? What are the benefits?


<CTA_Middle_Frameworks />



## Why Use Parallel Routes
One of the primary advantages of parallel routes is their ability to enable independent code rendering on the same URL and within the same view, utilizing slots. This offers a significant improvement over traditional routing methods, which were limited to linear rendering – meaning a single URL could only be associated with a single view.

As a result, developers had to resort to component composition techniques to create dynamic and adaptable user interfaces. One technique involved breaking down UI elements into smaller, modular components. These components could be combined and arranged in various ways to form complex layouts. While this approach has proven effective, managing complexity and ensuring optimal performance can still be challenging.

Parallel routes offer a fresh perspective on this challenge by allowing developers to define multiple routes that can coexist within the same view and still have its own set of slot definitions. This enables a more flexible and efficient approach to building dynamic user interfaces. Also, developers can now independently create and arrange discrete sections of a page while still enjoying the benefits of a unified routing system.

While all these are a great advantage to using parallel routes, the true benefits of using parallel routes include the following:
- Independent Route Handling
- Sub-navigation

Let's expound on this:

### Independent Routing
Parallel routes allow each route to be handled independently, allowing for granular control over loading and error states for different layout sections. This is particularly beneficial in scenarios where different sections of a page load at varying speeds or encounter unique errors.

For example, imagine a dashboard application that displays the users, the teams, and notifications(our example). If the data in the users' section takes longer to load, the dashboard can display a loading spinner or a UI specifically for that section. At the same time, the teams and notifications remain interactive.

Similarly, if there's an error fetching the teams section, an error message can be shown in that section without affecting the rest of the dashboard. 

This level of detail in handling states improves the user experience by providing immediate feedback and simplifies debugging and maintenance by isolating issues to specific sections. 

Let's add some loading UI to our application to show how this works.

We'll first need to create a delay function to add different delays to each slot's load times. 

In the `app` folder, create a folder called `lib` and a file called `utils.ts`inside this folder. Add the following code:
```javascript
export async function delay(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
```
The delay function will be used to pause the execution of the code for a certain amount of time.

Create a file called `loading.tsx` inside the `app` directory  and add the following code:
```javascript
import React from "react";

export default function Loading() {
  return (
    <div className="h-60 flex-1 animate-pulse rounded-xl bg-sky-800 p-10 text-white" />
  );
}
```
Let's also give each of the slots their loading UI. We'll start with the `@users` slot. Inside it, create a file called `loading.tsx`. Add the following code:
```javascript
import React from "react";

export default function LoadingUsers() {
  return (
    <div className="h-60 flex-1 animate-pulse rounded-xl bg-yellow-800 p-10 text-white" />
  );
}
```
Next up is the `@team` slot. Inside it, create the `loading.tsx` file. Add the following code:
```javascript
export default function LoadingTeam() {
  return (
    <div className="flex h-96 flex-1 animate-pulse items-center justify-center rounded-2xl bg-green-800 p-10 text-white" />
  );
}
```
Lastly, for the `@notification` slot, create the `loading.tsx` file inside it. Add the following code:
```javascript
export default function LoadingDashboard() {
  return (
    <div className="flex h-96 flex-1 animate-pulse items-center justify-center rounded-2xl bg-purple-800 p-10 text-white" />
  );
}
```
We need to update the `page.tsx` for each slot:

`@users/page.tsx`:
```javascript
import { delay } from "@/app/lib/utils";

export default async function Users() {
  await delay(2000);

  return (
    <div className="h-60 rounded-xl bg-yellow-800 p-10 text-white">
      <h1 className="text-3xl font-bold">Users</h1>
    </div>
  );
}
```
`@team/page.tsx`:
```javascript
import { delay } from "@/lib/utils";

export default async function Team() {
  await delay(3000);

  return (
    <div className="h-96 flex-1 rounded-2xl bg-green-800 p-10 text-white">
      <h2 className="text-xl font-semibold">Team</h2>
    </div>
  );
}
```
For `
@notications/page.tsx`:
```javascript
import { delay } from "@/app/lib/utils";

export default async function Dashboard() {
  await delay(5000);

  return (
    <div className="h-96 flex-1 rounded-2xl bg-purple-800 p-10 text-white">
      <h2 className="text-xl font-semibold">Dashboard</h2>
    </div>
  );
}
```
We updated each of these components to load at different milliseconds.

When we check the result in our browser, we should see we have a specific loading UI for each of our slots:
![Loading UI](https://blog.openreplay.com/images/parallel-routes-in-nextjs/images/image3.gif)
This shows that these are independent pages that can fetch specific data, their loading UI, and error boundaries as separate components or pages. Depending on your use case, you can render them simultaneously, side by side, or conditionally.
### Sub-navigation
This is another benefit of using parallel routes, as it facilitates a smooth sub-navigation experience within each route. 

Think of each slot on your dashboard functioning as a mini-app, equipped with its own navigation and state management capabilities. This feature proves useful in a complex application like the dashboard example we created, where different sections serve their own purposes.

Let's say each section of the dashboard, such as the users, team, or notifications, operates independently. Users can interact with each component separately without affecting the UI display or structure of the other sections.

For example, if we wish to implement sub-navigation within the `@notification` slot, we must create a sub-folder and then feature the link: `localhost:3000/subfolder` inside a header tag. We would navigate to the sub-folder and another link: `localhost:3000` in the header that navigates back to the notification default view.

Let's illustrate this concept using our example. Inside our `@notification` slot,  create a folder called `settings,` and inside it, create a file called `page.tsx` with the following content:
```javascript
import { delay } from "@/lib/utils";

export const dynamic = "force-dynamic";

export default async function Settings() {
  await delay(5000);

  return (
    <div className="h-96 flex-1 rounded-2xl bg-purple-800 p-10 text-white">
      <h2 className="text-xl font-semibold">Settings</h2>
    </div>
  );
}
```
We need a header to help us navigate to the `settings` segment. Create a folder called `components` inside our' app' folder. Inside it, create a file called `header.tsx` with the following code:
```javascript
import Link from "next/link";

export default function Header() {
  return (
    <header className="py-10">
      <div className="container">
        <nav>
          <ul className="flex items-center justify-center gap-10 text-lg font-bold uppercase tracking-wider text-gray-500">
            <li>
              <Link href="/">Home</Link>
            </li>
            <li>
              <Link href="/settings">Settings</Link>
            </li>
          </ul>
        </nav>
      </div>
    </header>
  );
}
```
You'll then need to add the header component to our layout. Go back to your `layout.tsx` and update the code to this:
```javascript
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import Header from "@/app/components/header";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
  team,
  notifications,
  users,
}: {
  children: React.ReactNode;
  team: React.ReactNode;
  notifications: React.ReactNode;
  users: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <main className="container">
          <section className="py-6">{children}</section>
          <Header />
          <section className="flex flex-col h-full gap-6 ms-12">
            {/* User component occupies the full width */}
            <div className="flex gap-6">
              <div className="w-full">{users}</div>
            </div>

            {/* Team and Notification components are stacked below */}
            <div className="flex gap-6">
              <div className="w-full">{team}</div>
              <div className="w-full">{notifications}</div>
            </div>
          </section>
        </main>
      </body>
    </html>
  );
}
```
Refresh the browser and navigate to the `settings` segment in the headers. You'll notice that only the `dashboard` slot changes, as it's the only one with the `settings` segment. 

The other slots do not possess that specific segment, so they will render their previously active state.

## Unmatched routes
We encounter an unmatched route when the contents of a slot do not match the current URL. This occurs during sub-navigation when only one dashboard section matches the current route.

So by default, our slots are rendered when we navigate to `localhost:3000`. But when we visit `localhost:3000/settings`, only the notifications slot has a corresponding route. The other three slots—children, users, and team—all become unmatched.

When a page reload occurs, Next.js searches for a [default.tsx](https://nextjs.org/docs/app/api-reference/file-conventions/default) file within the unmatched slots. If the file is absent, it returns a  404 error; otherwise, it displays the contents of the file.

When Next.js is unable to retrieve a slot's current state, the `default.tsx` file is used as a fallback for these unmatched slots. This allows us to render alternative content.

To prevent Next.js from returning a 404 error when accessing the `/settings` route within the `@notification` slot, add a `default.tsx` file to each slot within the route segment, including the `children` slot.

Let's showcase that in the project. If you try to refresh the page while it's in the `settings` segment, you'll encounter a  404 error. This happens because Next.js cannot find the `default.tsx` file for the `team`,`users`, or `dashboard` slots. To handle this, we need to create a `default.tsx` file inside the `teams`,`users`, and the root app folder.

Inside the `team` folder, create a `default.tsx` file with the following code:
```javascript
import { delay } from "@/app/lib/utils";

export default async function DefaultTeam() {
  await delay(3000);

  return (
    <div className="h-96 flex-1 rounded-2xl bg-green-800 p-10 text-white">
      <h2 className="text-xl font-semibold">Default Team</h2>
    </div>
  );
}
```
Inside the `users` folder, create a `default.tsx` file with the following code:
```javascript
import { delay } from "@/app/lib/utils";

export default async function Users() {
  await delay(2000);

  return (
    <div className="h-60 rounded-xl bg-yellow-800 p-10 text-white">
      <h1 className="text-3xl font-bold">DefaultUsers</h1>
    </div>
  );
}
```
Inside the root `app` folder, create a `default.tsx` for it also.
```javascript
import { delay } from "@/app/lib/utils";

export default async function Dashboard() {
  await delay(1000);

  return (
    <div>
      <h1 className="text-3xl font-bold">Default mainpage</h1>
    </div>
  );
}
```
Note that these changes won't be reflected in the development mode due to an issue known by Next.js, but these changes can be seen if we are in production mode. Let's do just that. Run the build command:
```bash
npm run build
```
And then start it in production with this command:
```bash
npm run start
```
![unmatched routes](https://blog.openreplay.com/images/parallel-routes-in-nextjs/images/image4.gif)
With these changes, our application should now handle unmatched routes gracefully, providing a consistent user experience. This is a powerful feature of parallel routes in Next.js 14, allowing developers to manage complex routing patterns easily.
## Conditional Routes
Parallel routes allow us to implement conditional routing. For example, if a user is authenticated, then the user is granted access to the dashboard layout. However, if the user is not authenticated, the user can be redirected to the login page. Here's an example of how we can utilize parallel routes:
```javascript
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import Header from "@/app/components/header";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

function handleLogin() {
  // Implement your login logic here
  console.log("Login initiated");
}

export default function RootLayout({
  children,
  team,
  notifications,
  users,
  login,
}: {
  children: React.ReactNode;
  team: React.ReactNode;
  notifications: React.ReactNode;
  users: React.ReactNode;
  login: React.ReactNode;
}) {
  //  Replace this with your actual authentication logic.
  const isLoggedIn = false;

  return isLoggedIn ? (
    <html lang="en">
      <body className={inter.className}>
        <main className="container">
          <section className="py-6">{children}</section>
          <Header />
          <section className="flex flex-col h-full gap-6 ms-12">
            {/* User component occupies the full width */}
            <div className="flex gap-6">
              <div className="w-full">{users}</div>
            </div>
            {/* Team and Notification components are stacked below */}
            <div className="flex gap-6">
              <div className="w-full">{team}</div>
              <div className="w-full">{notifications}</div>
            </div>
          </section>
        </main>
      </body>
    </html>
  ) : (
    <div>
      {/* Render the login component */}
      {login}
      {/* Optionally, include a button or form to initiate the login process */}
      <button onClick={handleLogin}>Log In</button>
    </div>
  );
}
```
In the code above, we implemented conditional routing to manage the user interface based on the user's authentication state. Specifically, if the user is logged in, the application renders a layout with a header and its content. If the user is not logged in, it renders a login component instead.
## Conclusion
Parallel routes in Next.js 14 provide a robust solution for managing complex routing patterns in dynamic applications. They offer a way to handle active states and navigation, handle unmatched routes, implement conditional routing, and define independent loading and error UI for each route. 

By leveraging these features, developers can build more sophisticated and user-friendly applications with Next.js.