- Published on
Intercepter les routes dans Next.js
Apprendre à intercepter les routes dans un projet Next.js et utiliser le paramètre `locale` pour les traduire.
Pour rappel dans Next.js, le routage est basé sur les dossiers dans le dossier app
(pour plus d'informations, consultez la structure de l'app directory).
Qu'est-ce que l'interception des routes ?
Concrètement, l'interception des routes permet de charger une route depuis une autre partie de l'application dans le layout actuel. Ce paradigme de routage est utile lorsqu'on souhaite afficher le contenu d'une route sans que l'utilisateur ne passe à un autre contexte.
Exemple d'une galerie d'images
Imaginons que vous souhaitiez afficher une galerie d'images dans votre application Next.js. Vous aimeriez que l'utilisateur puisse cliquer sur une image pour la voir en plein écran, accompagnée d'un descriptif détaillé.
Au premier abord, un composant dynamique couplé à une modale pourrait sembler être une bonne solution. Cependant, les robots crawleurs ne lisent que les contenus statiques. Donc si l'on souhaite que notre contenu photographique soit indexé par les moteurs de recherche, l'interception des routes devient une meilleure option.
Configuration de base
# URL
http://localhost:3000/
# Structure de l'app directory
app
├── @modal
│ ├── (.)photo
│ │ └── [id]
│ │ └── page.tsx
│ └── default.tsx
├── photo
│ └── [id]
│ └── page.tsx
├── default.tsx
├── layout.tsx
└── page.tsx
Dans cette structure, plusieurs éléments sont importants :
-
Le dossier
/app/@modal
, ne contient pas de fichierpage.tsx
et n'est pas considéré comme une route à part entière. Il s'agit d'une route parallèle appelée slots qui permet d'injecter des composants dans le layout sans affecter les segments d'URL. (Pour plus de détails, consultez la documentation de Next.js).
Ce qui nous intéresse ici c'est le dossier/app/@modal/(.)photo/[id]
. Il contient une particuliraté, son préfixe (.) (équivalent du relative path ./). Il fait référence à un segment du niveau supérieur, en l'occurrence le dossier/app/photo/[id]
. C'est ici que l'on intercepte la route. -
Le dossier
/app/photo/[id]
. C'est une route dynamique, elle prendra en paramètre l'id de la photo.
Passons au code
Voici comment sont structurés les fichiers de l'application.
app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import Header from "@/components/Header";
import DotPattern from "@/components/magicui/dot-pattern";
import { cn } from "@/lib/utils";
import Footer from "@/components/Footer";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Benmehal Joris",
description: "Next.js sandbox",
};
export default function RootLayout({
children,
modal,
}: Readonly<{
children: React.ReactNode;
modal: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`relative w-full flex-col items-center justify-center overflow-hidden ${inter.className}`}>
{/* <RetroGrid /> */}
<DotPattern
className={cn(
"-z-10 [mask-image:radial-gradient(600px_circle_at_center,white,transparent)]"
)}
/>
<Header />
<section className="container items-center justify-center flex h-screen mx-auto">
{children}
</section>
{modal}
<Footer />
</body>
</html>
);
}
Ici, le prop modal
est injecté dans le layout. Comme expliqué précédemment, il s'agit d'une route parallèle permettant à l'utilisateur de consulter une image de la galerie en premier plan sans changer de contexte.
app/page.tsx
import Galery from "@/components/Galery";
export default function Page() {
return <Galery />;
}
Le fichier page.tsx
est une page statique qui affiche la galerie d'images.
app/components/Galery
"use client";
import ImageCard from "./ImageCard";
import { datas } from "@/data/images";
export default function Galery() {
return (
<div className="w-full flex gap-8 flex-col">
<h1 className="text-center text-5xl font-bold">Gallery</h1>
<div className="grid grid-cols-2 gap-8">
{datas.map((data) => (
<ImageCard key={data.id} id={data.id} src={data.thumbnail} />
))}
</div>
</div>
);
}
Ce composant nous permet d'afficher nos images.
app/components/ImageCard
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
} from "@/components/ui/card";
import Image from "next/image";
import Link from "next/link";
interface ImageCardProps {
id: number;
src: string;
title?: string;
description?: string;
}
export default function ImageCard({
id,
src,
title,
description,
}: ImageCardProps) {
return (
<Link href={`/photo/${id}`} passHref>
<Card className="max-w-2xl mx-auto overflow-hidden bg-white">
<div className="relative w-full h-64">
<Image
src={src}
alt={title || "No title"}
layout="fill"
objectFit="cover"
/>
</div>
{title && description && (
<>
<CardHeader>
<CardTitle>{title}</CardTitle>
</CardHeader>
<CardContent>
<CardDescription>{description}</CardDescription>
</CardContent>
</>
)}
</Card>
</Link>
);
}
Le composant ImageCard
, utilise next/link
pour créer un lien vers la page /photo/${id}
, qpermettant la redirection vers la page photo/[id]/page.tsx
.
app/photo/[id]/page.tsx
import ImageCard from "@/components/ImageCard";
import { datas } from "@/data/images";
export function generateStaticParams(): Promise<{ id: string }[]> {
return Promise.resolve(datas.map((data) => ({ id: data.id.toString() })));
}
export default function PhotoPage({
params: { id },
}: {
params: { id: string };
}) {
const data = datas.find((data) => Number(data.id) === Number(id));
if (!data) {
return <div>Not found</div>;
}
return (
<div className="flex flex-col gap-2 h-screen w-full text-center justify-center p-4 rounded-lg">
<h1 className="text-center text-5xl font-bold">Photo {data.id}</h1>
<ImageCard
id={data.id}
src={data.thumbnail}
title={data.title}
description={data.description}
/>
</div>
);
}
Cette route dynamique affiche l'image individuellement. Elle permet également d'accéder à la page via une URL comme http://localhost:3000/photo/1
. La fonction generateStaticParams génère les pages statiques pour les routes dynamiques au moment du build.
app/@modal/(.)photo/[id]/page.tsx
import ImageCard from "@/components/ImageCard";
import Modal from "@/components/Modal";
import { datas } from "@/data/images";
export default function PhotoModal({
params: { id: photoId },
}: {
params: { id: string };
}) {
const photo = datas.find((p) => p.id === Number(photoId)) || datas[0];
return (
<Modal>
<div className="w-full text-center max-w-2xl rounded-lg">
<ImageCard
id={photo.id}
src={photo.thumbnail}
title={photo.title}
description={photo.description}
/>
</div>
</Modal>
);
}
C'est ici que l'on intercepte la route et que l'on redirige vers la page photo/[id]/page.tsx
. Le container Modal
permet de rendre la modale avec la création d'un portal React dans le DOM.
Que se passe-t-il si l'utilisateur clique sur une image ?
# URL
http://localhost:3000/photo/1
L'URL change, mais l'on reste sur la même page, dans le même contexte. C'est là que l'interception des routes prend tout son sens.
Elle nous permet de :
- Rendre le contenu modal partageable via une URL.
- Préserver le contexte lorsque la page est actualisée, au lieu de fermer la modale.
- Fermer la modale lors de la navigation arrière, au lieu de revenir à la route précédente.
- Réouvrir la modale lors de la navigation avant.
Et si la page est actualisée ?
# URL
http://localhost:3000/photo/1
L'URL reste la même, mais la mise en page change, car nous sommes maintenant sur la page photo/[id]/page.tsx
qui est une page statique.
Résultat
Conclusion
L'interception des routes dans Next.js est un outil puissant pour améliorer l'expérience utilisateur, tout en préservant le contexte et en optimisant le référencement. Elle permet de gérer efficacement les modales en les rendant partageables et accessibles via des URLs, tout en maintenant une navigation fluide au sein de l'application.