Conseils4 MIN DE LECTURE

Gérer les erreurs et le chargement en Next.js : ce qui sépare une démo d'un vrai produit

Le code Next.js qui plante en prod, c'est rarement la logique — c'est ce qu'on oublie de gérer. error.tsx, loading.tsx, états vides, échecs de Server Actions : le guide pour livrer un vrai produit.

Ordinateur portable ouvert dans le noir, clavier et écran éclairés de néons rose et bleu, avec le titre « La minute code »
Sommaire

Le code qui plante en prod, c'est rarement la logique

Pendant longtemps, je codais uniquement le happy path. L'API répond, la donnée est là, tout roule. Sauf que non : l'API tombe, l'utilisateur perd sa connexion, la requête met huit secondes. Et là, l'app affiche une page blanche ou un spinner infini. Un utilisateur ne juge pas ton app quand tout marche. Il la juge le jour où ça casse.

Le happy path, c'est 20% du boulot. Les 80% restants, c'est tout ce qui peut mal tourner. En Next.js App Router, la bonne nouvelle, c'est que le framework te donne des conventions dédiées. Encore faut-il les utiliser.

error.tsx : capturer les erreurs par section

L'erreur classique : un seul gestionnaire d'erreur global, tout en haut. Quand un morceau plante, c'est toute la page qui saute. Dans l'App Router, tu poses un fichier error.tsx au niveau de chaque section critique. Next.js l'utilise comme frontière : seule cette portion bascule en erreur, le reste de la page continue de vivre. Note que error.tsx est forcément un Client Component.

app/dashboard/error.tsxTSX / React
"use client";

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div className="p-6 text-center">
      <p className="mb-4">Impossible de charger le tableau de bord.</p>
      <button
        onClick={() => reset()}
        className="rounded bg-black px-4 py-2 text-white"
      >
        Réessayer
      </button>
    </div>
  );
}

loading.tsx : afficher vite au lieu d'attendre

Sans état de chargement, l'écran reste figé pendant le fetch. Aucun retour, l'utilisateur croit que ça a planté. Un fichier loading.tsx transforme automatiquement ta page en Suspense boundary : Next.js affiche le fallback pendant que le Server Component récupère la donnée, puis stream le vrai contenu. Zéro configuration.

app/dashboard/loading.tsxTSX / React
export default function Loading() {
  return (
    <div className="space-y-3 p-6">
      <div className="h-6 w-1/3 animate-pulse rounded bg-gray-200" />
      <div className="h-24 animate-pulse rounded bg-gray-200" />
      <div className="h-24 animate-pulse rounded bg-gray-200" />
    </div>
  );
}

Un skeleton qui reprend la forme du contenu final donne une sensation de rapidité bien supérieure à un spinner centré. Tu ne réduis pas le temps de chargement, mais tu réduis le temps de chargement perçu.

Les états vides comptent autant que les erreurs

Une requête peut réussir et ne rien renvoyer. Liste vide, recherche sans résultat, premier lancement. Trop de produits affichent alors une zone blanche déroutante. L'état vide, ce n'est pas juste "aucune donnée" : c'est une occasion de guider l'utilisateur vers l'action suivante.

app/projets/page.tsxTSX / React
export default async function Projets() {
  const projets = await getProjets();

  if (projets.length === 0) {
    return (
      <div className="p-8 text-center">
        <p className="mb-3">Aucun projet pour l'instant.</p>
        <a href="/projets/nouveau" className="underline">
          Créer ton premier projet
        </a>
      </div>
    );
  }

  return <ProjetList projets={projets} />;
}

Server Actions : gérer l'échec proprement

Une Server Action qui suppose que tout se passe bien, c'est la garantie d'un bug silencieux. Renvoie toujours un état clair, succès comme échec, et laisse le client afficher le bon message avec la possibilité de réessayer.

app/actions.tsTSX / React
"use server";

export async function creerProjet(_prev: unknown, formData: FormData) {
  const nom = formData.get("nom");

  if (!nom) {
    return { ok: false, message: "Le nom est requis." };
  }

  try {
    await db.projet.create({ data: { nom: String(nom) } });
    return { ok: true, message: "Projet créé." };
  } catch {
    return { ok: false, message: "Échec côté serveur. Réessaie." };
  }
}

Le happy path, c'est 20% du boulot. Les 80% restants — erreurs, chargement, états vides — c'est exactement ce qui sépare une démo d'un vrai produit.

Checklist avant deploy

Avant de mettre en ligne, je vérifie : un error.tsx sur chaque section critique, pas un seul global. Un loading.tsx pour chaque page qui fetch de la donnée. Des états vides pensés, avec une action de sortie. Chaque Server Action renvoie un état explicite, succès comme échec. Et un chemin de retour clair quand quelque chose échoue : l'utilisateur doit toujours pouvoir réessayer sans recharger toute la page. Code tes erreurs avant tes features — ton toi du futur te dira merci.

Partager
À lire aussi
08Deux formules, un appel

30 minutes pour décider
si on travaille ensemble.

Réserver mon appel