Creating Modals in Next.js 14: Using Parallel and Intercepting Routes

Abdul-Majid AladejanaAbdul-Majid Aladejana
Oct 09, 2024·
7 min read

Modal

A Modal is an interactive window that displays on top of the main window, blocking interaction until closed. They are often used to provide important information or gather user input without the need for full-page navigation. This graphical control element prevents users from taking action until they acknowledge the information or complete the task. You can implement React modals using third-party libraries, custom components, or React portals.

However, integrating modals with modern routing has often been challenging. Next.js 14 introduces parallel and intercepting routes, offering you a revolutionary approach to creating modal interfaces. These new routing features address the challenges you encounter when integrating modals with traditional routing patterns.

In this article, you will learn Next.js 14's parallel and intercepting routes and how to create effective modal interfaces using them.

Prerequisites

You will need the knowledge of the following to complete this tutorial:

  • React fundamentals

  • Next.js basics

Understanding the Building Blocks

You may notice that the files end in .tsx suffix. This is because the project is written in TypeScript. If you are unfamiliar with TypeScript, kindly continue with the .jsx suffix.

What are parallel routes?

Parallel routes are advance routing features in Next.js. They enable you to render multiple pages simultaneously or conditionally within a shared layout. This results in a more fluid experience, where users can switch between views or sections without full-page reloads or significant navigation interruptions.

Parallel Routes in Next.js are implemented by defining "slots," which are folders named with an '@' prefix before the folder name, and adding a page.tsx (or .jsx if you do not use typescript). Each parallel route can also have its own loading, error, and not-found states. These slots are then passed as props alongside the children components to a shared parent layout component.

Take the following steps to integrate parallel routes in your app:

  1. Install your Next.js app
npx create-next-app@latest
  1. In your app directory, create folders with the @ prefix (like @folder and @parallel) and add page.tsx

component tree with parallel route

const page = () => {
  return <div>Parallelroute</div>;
};
export default page;
  1. Define your layout component in app/layout.js to accept the @parallelroute slot as props, then render them alongside the children prop. Here's an example of the layout component:
export default function RootLayout({
  children,
  parallelroute,
}: Readonly<{
  children: React.ReactNode;
  parallelroute: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>
        {children}
        {parallelroute}
      </body>
    </html>
  );
}

Keep in mind that slots do not impact the URL structure and are not treated as route segments. For example, a route like /modal/@parallelroutes would remain the same as /modal, even if @parallelroutes is a slot.

Best Practices when using Parallel routes

  • Use meaningful names for parallel route folders: When creating your parallel route folders, it is important to give them descriptive names that reflect their purpose (e.g., @admin, @user, @feed). This makes it easier for you to understand the function of each route at a glance and enhances code readability.

  • Handle loading and error states for each route: Since parallel routes are independently streamed from the other children components, it's important to define custom loading and error states for each one. This ensures a smoother user experience, providing users with appropriate feedback (like a loading spinner or an error message) while a route is being fetched or when something goes wrong.

  • Create a default.tsx or [...allroutes] Route: It’s a best practice to have a fallback or default route, such as default.tsx or [...allroutes].tsx, within your parallel route folders. This ensures that when a route doesn’t exactly match any defined paths, the app will render a sensible fallback (like a "page not found" message or a default view).

Parallel routing provides the following benefits:

  • It lets users switch views easily without reloading the page

  • It ensures better code organisation by splitting different views into separate files and make the site more responsive by reducing the loading time

  • Ensures that the layout stays consistent across different sections

  • Support dynamic content rendering based on user roles or actions

  • Independent error and loading states for each parallel route

Parallel route is particularly useful for the following:

  • Modals Split views (like dashboards)

  • Social media feeds with chat sidebars

  • Conditional rendering based on certain states

  • Documentation sites with navigation and content areas

What are Intercepting Routes?

Intercepting routes is a feature in Next.js that overrides or "intercepts" the transmission to the default navigation page. This feature allows you to display different content while preserving the URL. For example, when a user clicks a link that opens a new page, you can intercept the route to display another page while retaining the URL, such as /page/modal. This creates a more dynamic, app-like experience where users can interact with different parts of the app without being redirected to an entirely new page.

To implement intercepting routes, create a new folder using the intercepting route convention. This new folder, known as the intercepting folder, should begin with a (.), (..), or (...) convention, similar to the relative path convention ../, followed by the name of the folder you wish to intercept.

You can use any of the following syntax:

  • (.) intercept segments on the same level

  • (..) intercept segments at one level higher

  • (..)(..) intercept segments two levels higher

  • (...) intercept segments from the root app directory

Take the following steps to integrate parallel routes in your app:

  1. Create a folder with your intended syntax within the folder from which you plan to start intercepting. For example, to intercept the about route in the contact route, create (..)about route within the contact route and add a page.tsx.

Intercepting Route

  1. You can add your code or use this as a demo in the About, Contact and intercepting About Page

About page

const Aboutpage = () => {
  return <div>Aboutpage</div>;
};
export default Aboutpage;

Contact Page

import Link from "next/link";
const page = () => {
  return (
    <div>
      <Link href="/about">
<div>Contact Page</div>
</Link>
    </div>
  );
};
export default page;

Intercepted About Page

const InterceptedAboutpage = () => {
  return <div>Intercepted Aboutpage</div>;
};
export default  InterceptedAboutpage;

Best Practices

  • Use Meaningful Names with Syntax Conventions: Ensure that your route names convey the page you plan to intercept and use the correct intercepting route syntax.

  • Organise routes that share similar contexts or features under common parent routes, especially for authentication or dashboard functionalities.

  • Avoid Deeply Nested Intercepting Routes: Overly nested routes complicate navigation and make it harder to maintain.

  • Don't overuse intercepting routes for simple navigation: not every route needs interception.

  • Use intercepting routes only when essential (e.g., authentication, role-based access control) and rely on standard routing for simple view changes.

Steps to implement a Modal with Parallel route and intercepting routes

Let's assume you intend to display your login page as a modal whenever your users navigate from the app directory.

  1. In the app directory, create a parallel route, as explained earlier.

    Add the parallel route slot to the layout.tsx page.

export default function RootLayout({
  children,
 modal,
}: Readonly<{
  children: React.ReactNode;
 modal: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body >
        {children}
        {parallelroute}
      </body>
    </html>
  );
}
  1. Within the parallel route, create an intercepting route named after the page you intend to intercept, e.g., (.)ogin page.

Intercepted login route

You can add your code or use this as a demo

"use client";
import { useState } from 'react';

export default function LoginPage() {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = (e:React.FormEvent) => {
    e.preventDefault();
    console.log('Data:', { username, password });
  };

  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-100">
      <div className="bg-white p-8 rounded-lg shadow-md w-96">
        <h2 className="text-2xl font-bold mb-6 text-center text-gray-800">Login</h2>
        <form onSubmit={handleSubmit} className="space-y-4">
          <div>
            <label htmlFor="username" className="block text-sm font-medium text-gray-700">Username</label>
            <input
              type="text"
              id="username"
              value={username}
              onChange={(e) => setUsername(e.target.value)}
              className="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
              required
            />
          </div>
          <div>
            <label htmlFor="password" className="block text-sm font-medium text-gray-700">Password</label>
            <input
              type="password"
              id="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              className="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
              required
            />
          </div>
          <button
            type="submit"
            className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
          >
            Log In
          </button>
        </form>
      </div>
    </div>
  );
}

You'll encounter an error if you run this application and navigate to http://localhost:3000/modal. This error occurs because you haven’t defined any routes for the @modal slot apart from the (.)login route. As a result, Next.js can't determine the state for the route other than /login. To fix this, add a default.tsx file inside the @modal folder. You can return whatever you wish or return null.

const page = () => {
  return null;
};
export default page;

Conclusion

A modal is an overlay window that appears over the main content, preventing user interaction until closed. Modals are commonly used for important information, collect user input, or require user acknowledgment.

16
Subscribe to my newsletter

Read articles from Abdul-Majid Aladejana directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Abdul-Majid Aladejana
Abdul-Majid Aladejana