Enhancing UX with Next.js Intercepted Routes
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
- Follow the installation guide on Shadcn UI's website.
- Add the dialog component by following the instructions on: Dialog 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.
- 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.
- Inside the
- Add specific modal routes:
- Inside the
@modal
folder, create a subfolder named(.)login
. - Within the
(.)login
folder, add apage.tsx
file.
- Inside the
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:
Key Takeaways
- Simplified UX: Route interception allows modals to function naturally without disrupting the flow.
- Easier State Management: Avoid complex global state logic to maintain context between pages.
- 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.