close button
Enhancing UX with Next.js Intercepted Routes
profile picture Abdul Kader Albaz Aqther A A
6 min read Feb 3, 2025
ux

Enhancing UX with Next.js Intercepted Routes

ux

What is Intercepting route?

Intercepting routes in Next.js enhance the user experience by allowing content from different pages to be displayed within the same layout. Instead of navigating to a new page, the content appears on top, such as showing a form or a notification without interrupting the current context. This approach is particularly helpful when you want to maintain the flow of the application and avoid unnecessary page reloads. By keeping the user in the same layout, intercepting routes make the application feel faster and more responsive.

Moreover, intercepting routes make it easier to manage UI elements that might normally be displayed in separate pages, such as overlays, modals, or notifications. Instead of having multiple routes and separate components, you can simply use the intercepting route feature to load content dynamically, improving both the performance and the smoothness of your app. This makes it easier to provide richer interactions without overwhelming the user with constant page changes.

By using intercepting routes, developers can create more engaging and interactive applications where context-switching is minimized. Whether it’s an image gallery, a product preview, or an alert box, intercepting routes make it simple to display content in a seamless, intuitive way. It also gives users more control over how they interact with the app, all while making the experience feel faster and more fluid.

Intercepting patterns

Intercepting route patterns in Next.js function like relative path conventions ../. which means we can define intercepting routes using different levels:

  • (..) targets routes on the same directory level.
  • (..)(..) accesses routes two levels higher.
  • (…) directly points to the application's root directory.

These patterns allow you to flexibly integrate routes from any location in your app, enabling seamless overlays or nested content rendering across layouts without disrupting the user's navigation context.

Implementing Route Interception

Step 1: Setting Up the Project

Start by creating a Next.js app with route interception enabled:

npx create-next-app@latest intercepting-routes
cd my-next-app

Step 2: Create a reusable modal component

Now, create a file modal.tsx inside the components folder with the given code:

"use client";
import { Dialog, DialogContent, DialogOverlay } from "@/components/ui/dialog";
import { useRouter } from "next/navigation";
export default function Modal({
    children,
    className
}: {
    children: React.ReactNode
    className?: string
}) {
    const router = useRouter();
    function handleOpenChange() {
        router.back();
    }

    return (
        <Dialog
            defaultOpen={true}
            open={true}
            onOpenChange={handleOpenChange}
        >
            <DialogContent
                className={className || ""}
            >
                {children}
            </DialogContent>
        </Dialog>
    )
}

Step 3: Create login page

Create a folder named login in the root of the app directory and add a page.tsx file with the given code:

"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 ">
     <div className="bg-[#27272A80] p-8 rounded-lg shadow-md w-96">
       <h2 className="text-2xl font-bold mb-6 text-center text-white">Login</h2>
       <form onSubmit={handleSubmit} className="space-y-4">
         <div>
           <label htmlFor="username" className="block text-sm font-medium text-white">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-transparent border border-[#2e2e2e] 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-white">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 border bg-transparent border-[#2e2e2e] rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
             required
           />
         </div>
         <button
           type="submit"
           className="!mt-10 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>
 );
}

Step 4: Create intercepting routes

Let's create separate slots to handle intercepting routes and display modals instead of redirecting.

  1. Create a dedicated slot for modals:
    • Inside the app directory, create a folder named @modal.
    • This folder will serve as a container for modal routes.
  2. Add specific modal routes:
    • Inside the @modal folder, create a subfolder named (.)login.
    • Within the (.)login folder, add a page.tsx file.
src/
├── app/
│   ├── @modal/
│   │   ├── (.)login/
│   │   │   └── page.tsx
│   ├── login/
│   │   └── page.tsx 
│   ├── favicon.ico
│   ├── globals.css
│   ├── layout.tsx
│   └── page.tsx

Add the following code inside (.)login/page.tsx

"use client";
import Modal from "@/components/modal";
import { useState } from "react";

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

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

   return (
       <Modal
           className="w-96"
       >
           <div className=" rounded-lg  w-full">
               <h2 className="text-2xl font-bold mb-6 text-center text-gray-800">Intercepted 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-transparent 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-transparent 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="!mt-10 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>
       </Modal>
   )
}


This modal and content will be displayed by intercepting the /login route \

Step 5: Add @modal slot to layout.tsx and a default.tsx file within the @modal slot with the following code:

//app/layout.tsx

import React from "react";

export default function layout({
  children,
  modal,
}: {
  children: React.ReactNode;
  modal: React.ReactNode;
}) {
  return (
    <div>
      {children}
      {modal}
    </div>
  );
}

// app/@modal/default.tsx

Export const Default = () => {
  return null;
};


We created the default.tsx file to prevent Next.js from throwing a 404 error when the modal is inactive. Since we don’t want to display anything when it’s not active, we return null.

Now the modal should render properly after interception:

gif.gif

Key Takeaways

  1. Simplified UX: Route interception allows modals to function naturally without disrupting the flow.
  2. Easier State Management: Avoid complex global state logic to maintain context between pages.
  3. Scalable Approach: The flexibility of intercepted routes supports various UX patterns, including previews and split views.

Conclusion

Next.js's route interception feature is a game-changer for dynamic navigation challenges. By using it to implement seamless modals, I improved both the user experience and the code's maintainability. Whether you’re working on e-commerce, content platforms, or dashboards, route interception can help you deliver an enhanced, intuitive UX.

Application Modernization Icon

Explore limitless possibilities with AntStack's frontend development capabilities. Empowering your business to achieve your most audacious goals. Build better.

Talk to us

Author(s)

Tags

Your Digital Journey deserves a great story.

Build one with us.

Recommended Blogs

Cookies Icon

These cookies are used to collect information about how you interact with this website and allow us to remember you. We use this information to improve and customize your browsing experience, as well as for analytics.

If you decline, your information won’t be tracked when you visit this website. A single cookie will be used in your browser to remember your preference.

Talk to us