Leçon 17 / 20

Assembler la page catalogue filtrable

20 min Next.js — données, formulaires et cache Gratuit

Objectif : Livrer une page serveur qui affiche les randonnées et applique le filtre de difficulté.

Quand une personne choisit une difficulté, le catalogue doit afficher les randonnées correspondantes sans déplacer l’accès aux données dans le navigateur. Vous allez relier le formulaire à l’URL, puis utiliser ce paramètre pour charger et afficher les résultats côté serveur. Une réponse vide reste un état normal ; une panne du service doit, elle, rester une erreur.

Relier le formulaire au chargement

Dans l’App Router, votre page reste un composant serveur, comme expliqué dans « Repérer les composants serveur dans l’App Router ». Le formulaire envoie le champ `difficulte` à la route `/` avec la méthode GET. Le navigateur affiche donc, par exemple, `/?difficulte=facile`, et la page lit cette valeur à chaque requête.

Le code ci-dessous constitue le fichier complet `app/page.tsx`. Il suppose que `RANDONNEES_API_URL` désigne l’URL de l’endpoint qui renvoie le catalogue au format JSON : un tableau d’objets contenant `id`, `nom`, `difficulte` et `distance`. Le filtre est envoyé au service uniquement s’il appartient aux valeurs prévues ; l’URL du navigateur n’est pas considérée comme une donnée fiable.

app/page.tsxTexte
type Randonnee = {
  id: string;
  nom: string;
  difficulte: string;
  distance: number;
};

type Difficulte = 'facile' | 'intermediaire' | 'difficile';
type SearchParams = Promise<{ difficulte?: string | string[] }>;

const difficultes: Difficulte[] = ['facile', 'intermediaire', 'difficile'];

async function obtenirRandonnees({ difficulte }: { difficulte?: Difficulte }) {
  const apiUrl = process.env.RANDONNEES_API_URL;
  if (!apiUrl) {
    throw new Error('La variable RANDONNEES_API_URL est absente.');
  }

  const url = new URL(apiUrl);
  if (difficulte) {
    url.searchParams.set('difficulte', difficulte);
  }

  const reponse = await fetch(url, {
    next: { revalidate: 300 }
  });
  if (!reponse.ok) {
    throw new Error(`Échec du chargement du catalogue : ${reponse.status}`);
  }

  return await reponse.json() as Randonnee[];
}

export default async function CataloguePage({
  searchParams
}: {
  searchParams: SearchParams;
}) {
  const parametres = await searchParams;
  const valeur = parametres.difficulte;
  const candidate = Array.isArray(valeur) ? valeur[0] : valeur;
  const difficulte = difficultes.includes(candidate as Difficulte)
    ? candidate as Difficulte
    : undefined;
  const randonnees = await obtenirRandonnees({ difficulte });

  return (
    <>
      <h1 className="rando-titre">Randonnées à découvrir</h1>
      <form className="rando-recherche" action="/">
        <label htmlFor="difficulte">Difficulté</label>
        <select id="difficulte" name="difficulte" defaultValue={difficulte ?? ''}>
          <option value="">Toutes</option>
          <option value="facile">Facile</option>
          <option value="intermediaire">Intermédiaire</option>
          <option value="difficile">Difficile</option>
        </select>
        <button className="rando-bouton" type="submit">Rechercher</button>
      </form>
      {randonnees.length === 0 ? (
        <p className="rando-etat" role="status">
          Aucune randonnée ne correspond à cette recherche.
        </p>
      ) : (
        <ul className="rando-liste">
          {randonnees.map((randonnee) => (
            <li className="rando-carte" key={randonnee.id}>
              <a href={`/randonnees/${randonnee.id}`}>
                <h2>{randonnee.nom}</h2>
                <p>{randonnee.difficulte} · {randonnee.distance} km</p>
              </a>
            </li>
          ))}
        </ul>
      )}
    </>
  );
}

La propriété `searchParams` est asynchrone dans les versions récentes de Next.js ; vous la résolvez avant d’en lire la valeur. Le contrôle accepte uniquement les trois difficultés prévues. Si le paramètre est absent, invalide ou répété, le chargement se fait sans filtre et le formulaire revient à « Toutes ».

Vérifier le rendu et le filtre

Avec une réponse contenant deux randonnées faciles, la page affiche les cartes correspondantes et le formulaire conserve « Facile » comme sélection. Si le service renvoie un tableau vide, la page affiche le message d’absence de résultat. Vous pouvez vérifier le parcours en ouvrant `/`, en sélectionnant « Intermédiaire », puis en contrôlant que l’URL contient `?difficulte=intermediaire` et que la réponse du service ne contient que les randonnées de cette difficulté.

La requête utilise une revalidation de 300 secondes, conformément au réglage du cache vu dans « Mettre en cache les lectures ». La récupération et le rendu restent entièrement côté serveur ; le navigateur reçoit uniquement le HTML nécessaire à l’affichage. La prochaine leçon assemblera la fiche détail et ses avis.

À retenir

  • Le formulaire GET transmet la difficulté dans l’URL et permet de retrouver ou partager une recherche.
  • La page valide le paramètre avant de le transmettre à la requête serveur.
  • Une liste vide affiche un état dédié ; une erreur HTTP reste une erreur.
  • Les données et la configuration du service ne sont pas déplacées dans un composant client.

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.