Construire la fiche détail complète
Objectif : Livrer une route dynamique qui affiche une randonnée et ses avis depuis les données serveur, avec un traitement distinct d’une fiche absente et d’une randonnée sans avis.
Une fiche de randonnée doit réunir les informations de la randonnée et les avis qui lui sont associés. Si l’identifiant demandé n’existe pas, la route ne doit pas afficher une fiche vide ; si la randonnée existe sans avis, elle doit rester consultable. Vous allez assembler ces cas dans la route dynamique, en conservant l’accès aux données et le rendu sur le serveur.
Charger la randonnée et ses avis
La page du catalogue est déjà assemblée côté serveur et ses données passent par `RANDONNEES_API_URL`. Ici, le segment dynamique fournit l’identifiant à rechercher ; la réponse de détail doit contenir la randonnée et son tableau `avis`, afin que la page puisse présenter les deux sans requête depuis le navigateur. Conservez le traitement des erreurs HTTP : une randonnée introuvable déclenche `notFound()`, tandis qu’une erreur de réseau ou une autre réponse en échec doit remonter vers la frontière d’erreur de la route.
import { notFound } from 'next/navigation';
import { ajouterAvis } from '../actions/avis';
type Avis = {
id: string;
texte: string;
};
type Randonnee = {
id: string;
nom: string;
description: string;
difficulte: string;
distance: number;
avis: Avis[];
};
async function obtenirRandonnee(id: string): Promise<Randonnee | null> {
const baseUrl = process.env.RANDONNEES_API_URL;
if (!baseUrl) {
throw new Error('RANDONNEES_API_URL est absente.');
}
const reponse = await fetch(
`${baseUrl}/randonnees/${encodeURIComponent(id)}`,
{ next: { revalidate: 300 } }
);
if (reponse.status === 404) {
return null;
}
if (!reponse.ok) {
throw new Error(`Erreur de chargement de la randonnée : ${reponse.status}`);
}
return reponse.json() as Promise<Randonnee>;
}
export default async function RandonneePage({
params
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const randonnee = await obtenirRandonnee(id);
if (!randonnee) {
notFound();
}
return (
<article className="rando-fiche">
<h1 className="rando-titre">{randonnee.nom}</h1>
<p>{randonnee.description}</p>
<p>{randonnee.difficulte} · {randonnee.distance} km</p>
<section className="rando-avis" aria-labelledby="titre-avis">
<h2 id="titre-avis">Avis</h2>
{randonnee.avis.length === 0 ? (
<p className="rando-etat">Aucun avis pour le moment.</p>
) : (
randonnee.avis.map((avis) => <p key={avis.id}>{avis.texte}</p>)
)}
</section>
<form className="rando-formulaire" action={ajouterAvis}>
<input type="hidden" name="randonneeId" value={randonnee.id} />
<label htmlFor="texte-avis">Votre avis</label>
<textarea
className="rando-champ"
id="texte-avis"
name="texte"
required
/>
<button className="rando-bouton" type="submit">
Publier l’avis
</button>
</form>
</article>
);
}Ce fichier est complet pour `app/randonnees/[id]/page.tsx` et reprend l’action serveur déjà définie dans `app/actions/avis.ts`. L’import relatif `../actions/avis` remonte du dossier `[id]` vers `app`, puis rejoint le dossier `actions`. L’URL construite correspond à un service dont la base est fournie par `RANDONNEES_API_URL` et dont l’endpoint de détail suit la forme `/randonnees/{id}` ; adaptez la configuration du service à ce contrat, sans exposer cette variable au navigateur.
Distinguer absence de fiche et absence d’avis
Le statut HTTP 404 du service est converti en `null`, puis `notFound()` signale à Next.js que l’identifiant ne désigne aucune randonnée. Les autres réponses non réussies provoquent une exception ; elles ne sont donc pas présentées comme une absence de ressource. Ce traitement rejoint la gestion des ressources introuvables déjà étudiée pour le chargement d’une fiche.
À l’inverse, une randonnée existante peut avoir un tableau `avis` vide. Dans ce cas, la page affiche « Aucun avis pour le moment » et conserve le reste de la fiche. Si des avis sont présents, chaque commentaire est affiché avec sa clé `id`, ce qui permet à React d’identifier chaque élément de la liste. Le service doit renvoyer `avis` sous forme de tableau, y compris lorsqu’il n’y a aucun commentaire.
Relier la fiche au formulaire existant
Le formulaire transmet l’identifiant de la randonnée dans un champ caché et le texte dans `FormData`, comme dans la leçon consacrée à sa construction. Il reste relié à `ajouterAvis` : cette page n’ajoute pas de logique d’écriture ni de nouvel état client. La mise à jour de la fiche après la publication sera vérifiée dans la prochaine leçon consacrée à l’intégration du dépôt d’un avis.
À retenir
- La page dynamique charge la randonnée et ses avis côté serveur à partir de l’identifiant de route.
- Un statut 404 devient une fiche introuvable ; les autres échecs HTTP ou réseau restent des erreurs.
- Une randonnée sans avis reste affichée avec un message d’état vide propre à la section des avis.
- Le formulaire existant transmet l’identifiant et le commentaire à l’action serveur, sans accès aux données côté navigateur.
Dans la prochaine leçon, vous relierez le parcours complet du formulaire à l’écriture et vérifierez le résultat après mutation.