Leçon 14 / 15

Finaliser les pages de détail

12 min Next.js — pages, layout et rendu serveur Gratuit

Objectif : Afficher les informations et l’image correspondantes pour chaque itinéraire valide.

Une page de détail ne doit pas seulement afficher un titre : elle doit présenter les informations et le visuel correspondant à l’itinéraire demandé. Si le slug sélectionne la forêt mais que la page affiche l’image de la montagne, le catalogue devient incohérent.

Vous allez finaliser le fichier de route dynamique pour que chaque URL valide présente la bonne image, une description, une distance et une durée. La sélection par slug et le traitement de l’itinéraire introuvable ont déjà été introduits : vous les réutilisez ici.

Une même source de données pour les détails

La page d’accueil et la page de détail doivent s’appuyer sur les mêmes itinéraires. Chaque objet comporte donc un `slug` utilisé dans l’URL, des informations descriptives et les propriétés nécessaires à l’image : son chemin public, son texte alternatif et ses dimensions. Réutiliser ces valeurs évite de construire une image ou un texte différent selon la page consultée.

Dans l’état du projet, les deux itinéraires ont les slugs `foret` et `montagne`, et leurs images locales mesurent 1200 × 800 pixels. Le fichier ci-dessous rassemble les données dans la route de détail, pour rendre l’exemple complet et exécutable sans dépendre d’un fichier de données qui ne figure pas dans l’arborescence imposée.

Finaliser la route dynamique

Remplacez le contenu de `app/itineraires/[slug]/page.tsx` par le fichier complet ci-dessous. La fonction récupère le slug de l’URL, recherche l’objet correspondant et appelle `notFound()` si la recherche ne donne aucun résultat. Le JSX utilise ensuite cet objet unique pour remplir tous les détails, y compris l’image.

Texte
import Image from "next/image";
import Link from "next/link";
import { notFound } from "next/navigation";

type Itineraire = {
  slug: string;
  nom: string;
  resume: string;
  description: string;
  distance: string;
  duree: string;
  image: string;
  descriptionImage: string;
  largeur: number;
  hauteur: number;
};

const itineraires: Itineraire[] = [
  {
    slug: "foret",
    nom: "La forêt des cèdres",
    resume: "Un parcours ombragé au cœur des arbres.",
    description: "Parcourez un sentier forestier bordé de cèdres et profitez d’une marche à l’ombre.",
    distance: "6 km",
    duree: "2 h",
    image: "/images/foret.jpg",
    descriptionImage: "Sentier de randonnée traversant une forêt",
    largeur: 1200,
    hauteur: 800
  },
  {
    slug: "montagne",
    nom: "Le belvédère des sommets",
    resume: "Une montée qui ouvre sur les sommets.",
    description: "Suivez un itinéraire en altitude jusqu’à un belvédère offrant une vue sur les sommets.",
    distance: "9 km",
    duree: "3 h 30",
    image: "/images/montagne.jpg",
    descriptionImage: "Paysage de montagne visible depuis un sentier",
    largeur: 1200,
    hauteur: 800
  }
];

type PageProps = {
  params: Promise<{ slug: string }>;
};

export default async function Page({ params }: PageProps) {
  const { slug } = await params;
  const itineraire = itineraires.find((element) => element.slug === slug);

  if (!itineraire) {
    notFound();
  }

  return (
    <article className="sentier-carte">
      <Image
        className="sentier-image"
        src={itineraire.image}
        alt={itineraire.descriptionImage}
        width={itineraire.largeur}
        height={itineraire.hauteur}
      />
      <div className="sentier-details">
        <h1>{itineraire.nom}</h1>
        <p>{itineraire.description}</p>
        <p>Distance : {itineraire.distance}</p>
        <p>Durée : {itineraire.duree}</p>
      </div>
      <Link className="sentier-retour" href="/">Retour au catalogue</Link>
    </article>
  );
}

La route conserve les classes du contrat et affiche `Image` avec ses dimensions intrinsèques, comme sur la page d’accueil. Les chemins `/images/foret.jpg` et `/images/montagne.jpg` correspondent aux fichiers placés dans `public/images` ; le nom affiché, le texte alternatif et les caractéristiques de randonnée proviennent du même objet sélectionné.

Résultat selon l’URL

À l’adresse `/itineraires/foret`, la page présente « La forêt des cèdres », sa description, une distance de 6 km, une durée de 2 h et l’image de la forêt. À `/itineraires/montagne`, elle présente « Le belvédère des sommets », une distance de 9 km, une durée de 3 h 30 et l’image de montagne.

Un slug qui ne correspond à aucun objet déclenche toujours `notFound()` : la route ne présente donc pas par erreur les détails d’un autre itinéraire. Le composant `app/not-found.tsx` défini précédemment fournit l’affichage de cette situation.

À retenir

  • La page dynamique sélectionne un seul itinéraire à partir du slug de l’URL.
  • Le nom, la description, la distance, la durée et l’image proviennent de cet itinéraire sélectionné.
  • Les images locales conservent leurs chemins, textes alternatifs et dimensions intrinsèques.
  • Un slug inconnu continue de déclencher `notFound()`.

La leçon suivante vous permettra de tester les parcours du catalogue de bout en bout et de vérifier le comportement général de l’application.

Envie d'aller plus loin ?

Les cours Enovatic sont gratuits et le resteront. Si vous cherchez un accompagnement encadré, nos formations professionnelles sont animées par les mêmes formateurs.