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

Interception des routes - Home page

bash
# URL
http://localhost:3000/
bash
# 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 :

  1. Le dossier /app/@modal, ne contient pas de fichier page.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.

  2. 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
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
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
tsx
"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
tsx
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
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
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 ?

Interception des routes - Home page

bash
# URL
http://localhost:3000/photo/1

Interception des routes - Home page

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 ?

Interception des routes - Home page

bash
# 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

Interception des routes - Video

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.