Next.js

Modale Next.js Intercepting Routes : Formulaires Dashboard SaaS

Créez des modales Next.js avec intercepting routes, liens profonds et préservation d'état via parallel slots, Server Actions et shadcn/ui pour vos dashboards SaaS.

By Mohamed DjoudirMay 19, 20269 min read
Partager:
Modale Next.js Intercepting Routes : Formulaires Dashboard SaaS
#nextjs-intercepting-routes-modal#next.js-parallel-routes#shadcn-ui#server-actions#app-router

La création de tableaux de bord SaaS riches en données implique que les utilisateurs modifient fréquemment des enregistrements, consultent des profils ou ajustent des paramètres. Lorsque vous montez un dialogue React standard, vous piégez l'utilisateur : s'il rafraîchit la page, partage le lien ou clique sur le bouton retour du navigateur, il perd son contexte et l'état de votre application se brise. Pour résoudre ce problème, les développeurs ont besoin d'une modale de routes interceptées Next.js robuste.

Qu'est-ce qu'une modale de routes interceptées Next.js ?

Une modale de routes interceptées Next.js est un modèle de routage qui charge du contenu provenant d'une autre partie de l'application à l'intérieur de la mise en page (layout) actuelle, tout en préservant le contexte et en masquant l'URL.

Lorsqu'un utilisateur navigue vers /tasks/123, Next.js intercepte la requête et affiche l'interface de la tâche dans un slot de layout parallèle au-dessus de la vue actuelle. Cela offre l'expérience utilisateur d'un dialogue client rapide avec les avantages structurels d'une URL rendue côté serveur.

Cela résout le problème classique des applications mono-page (SPA) où les modales manquent de liens partageables et se brisent lorsque le bouton retour du navigateur est pressé. En liant la superposition à un segment de route réel, les développeurs maintiennent une capacité de lien profond sans sacrifier des transitions fluides et natives.

Pourquoi les modales React traditionnelles échouent dans les tableaux de bord SaaS

Historiquement, les développeurs front-end implémentaient des dialogues en basculant une variable d'état booléenne. Bien que très fonctionnel pour des alertes ou des confirmations simples, ce modèle s'effondre dans les environnements complexes de l'App Router de Next.js.

Imaginez un scénario où un utilisateur filtre un tableau de données de 500 lignes, applique des paramètres de recherche complexes et clique sur "Modifier" sur la ligne 42. Il s'attend à ce que le tableau reste intact en arrière-plan. S'il clique accidentellement sur le bouton retour du navigateur pour fermer la modale, un dialogue à état booléen le fera naviguer complètement hors du tableau de bord. De plus, les agents de support ne peuvent pas partager un lien direct vers cet enregistrement spécifique car l'URL n'a jamais changé.

Passer à une architecture pilotée par l'URL s'intègre nativement à la pile d'historique du navigateur. Comparons directement les deux approches.

Fonctionnalité Dialogue useState traditionnel Routes interceptées
Bouton retour navigateur Quitte toute l'application tableau de bord Ferme la modale en toute sécurité
Partage d'URL direct Impossible (nécessite un mapping manuel) Natif (navigue vers la page autonome)
Rafraîchissement de page Réinitialise entièrement l'état Affiche la vue spécifique de l'enregistrement
Contexte d'arrière-plan Inchangé visuellement, mais fragile Préservé nativement via les routes parallèles
Impact performance Code de la modale dans le bundle principal Code-splitting automatique par segment

Routes parallèles vs Routes interceptées Next.js : Quelle différence ?

Construire un dialogue piloté par l'URL nécessite deux fonctionnalités distinctes de Next.js travaillant ensemble : les routes parallèles et les routes interceptées. Bien que souvent utilisées de manière interchangeable, elles servent des objectifs totalement différents.

Les routes parallèles de Next.js utilisent des slots nommés, préfixés par un symbole @. Ces slots vous permettent d'afficher plusieurs pages simultanément dans le même layout. Lorsque l'utilisateur navigue, Next.js peut mettre à jour le slot parallèle sans restituer ou perdre l'état du slot principal children.

Cependant, une route parallèle seule ne fait que réserver un espace structurel. Nous devons également détourner l'intention de routage elle-même. C'est là qu'interviennent les routes interceptées. En utilisant des conventions de dossiers comme (.), (..), (..)(..), ou (...), vous demandez à Next.js d'intercepter un événement de navigation et d'afficher le contenu de destination dans un slot spécifique.

Définir les conventions du système de fichiers

Le dossier d'interception doit refléter le niveau relatif du segment cible dans la hiérarchie des routes.

  • (.) correspond aux segments au même niveau.
  • (..) correspond aux segments un niveau au-dessus.
  • (..)(..) correspond aux segments deux niveaux au-dessus.
  • (...) correspond aux segments depuis le répertoire root de l'application.

Voici une structure de dossiers prête pour la production cartographiant les routes standard et leurs équivalents interceptés.

app/
├── layout.tsx
├── @modal/                   // Le slot de route parallèle
│   ├── default.tsx           // Fallback pour les slots sans correspondance
│   └── (.)tasks/             // Interception de route au même niveau
│       └── [id]/
│           └── page.tsx      // Le composant UI de la modale
└── tasks/
    ├── page.tsx              // La vue principale d'arrière-plan
    └── [id]/
        └── page.tsx          // Vue autonome en page pleine

Dans cet arbre, (.)tasks/[id] intercepte la navigation fluide vers /tasks/[id]. Parce qu'il se trouve à l'intérieur du slot @modal, le composant intercepté s'affiche aux côtés de tasks/page.tsx.

Créer une modale d'édition partageable avec shadcn/ui Dialog

L'intégration d'une bibliothèque d'UI comme shadcn/ui nécessite un pont architectural spécifique. Radix UI, qui propulse les primitives de dialogue sous-jacentes, s'attend à contrôler son propre état de visibilité. Nous devons inverser ce contrôle pour que le routeur Next.js dicte le moment où la modale est montée ou démontée.

Étant donné que les routes interceptées ne s'appliquent qu'à la navigation fluide (clic sur un <Link> Next.js), notre dialogue doit s'ouvrir automatiquement lors du montage. Lorsque l'utilisateur interagit avec l'interface pour fermer la modale, nous utilisons le routeur pour rejeter la superposition.

Création du composant wrapper de modale

Vous devez définir defaultOpen={true} et surcharger le gestionnaire onOpenChange. Si le dialogue tente de se fermer, nous interceptons cet événement et déclenchons le parcours de l'historique. Cela retire l'élément de la pile du navigateur au lieu de simplement masquer visuellement les éléments du DOM.

"use client";

import { useRouter } from "next/navigation";
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
import { ReactNode } from "react";

interface InterceptedModalProps {
  children: ReactNode;
  title: string;
}

export function InterceptedModal({ children, title }: InterceptedModalProps) {
  const router = useRouter();

  const handleOpenChange = (isOpen: boolean) => {
    if (!isOpen) {
      // Quitte proprement la route interceptée en parcourant l'historique
      router.back();
    }
  };

  return (
    <Dialog defaultOpen={true} onOpenChange={handleOpenChange}>
      <DialogTitle className="sr-only">{title}</DialogTitle>
      <DialogContent className="sm:max-w-[600px]">
        {children}
      </DialogContent>
    </Dialog>
  );
}

Gérer la navigation forcée : Le fallback default.tsx

L'App Router de Next.js impose des règles de réconciliation strictes autour des slots de routes parallèles. Lorsqu'un utilisateur rafraîchit le navigateur ou colle une URL directement dans la barre d'adresse, le framework contourne entièrement l'intercepteur et affiche la vue autonome en page pleine.

Dans Next.js 16, les slots de routes parallèles ont besoin d'un fallback default.tsx lorsque le slot peut ne pas avoir de correspondance lors d'une navigation forcée. La documentation officielle précise qu'un fichier default.tsx est utilisé comme solution de repli lorsque Next.js ne peut pas récupérer l'état actif d'un slot après un chargement forcé. Ne pas fournir ce fichier pour un slot concerné provoque une erreur de build.

Le fichier default.tsx

Un fichier default.tsx retournant null à l'intérieur du slot @modal garantit que le slot reste masqué lorsqu'aucune route interceptée ne correspond.

// app/@modal/default.tsx
export default function ModalDefault() {
  // Retourne null pour garantir que la superposition reste invisible 
  // lorsqu'aucune route interceptée ne correspond activement à l'URL.
  return null;
}

Gérer les mutations de formulaire et les Server Actions dans la modale

Soumettre une Server Action à l'intérieur d'une modale interceptée nécessite une attention particulière. L'utilisation directe de revalidatePath peut réinitialiser le slot de mise en page prématurément.

Dans Next.js 16, l'approche moderne pour une cohérence immédiate est l'API updateTag. Alors que revalidateTag(tag, "max") est utilisé pour les mises à jour en arrière-plan (stale-while-revalidate), updateTag(tag) est conçu pour les scénarios de « lecture après écriture » comme les soumissions de formulaires où l'utilisateur attend un rafraîchissement instantané.

Fermeture sécurisée après soumission

Combinez une Server Action avec le parcours de l'historique côté client en utilisant le hook useActionState de React 19.

"use client";

import { useActionState, useEffect } from "react";
import { useRouter } from "next/navigation";
import { updateTaskAction } from "@/actions/tasks";
import { Button } from "@/components/ui/button";

export function EditTaskForm({ taskId, initialData }: { taskId: string, initialData: string }) {
  const router = useRouter();
  const [state, formAction, isPending] = useActionState(updateTaskAction, null);

  useEffect(() => {
    if (state?.success) {
      // Mutation réussie ; fermeture de la modale via l'historique
      router.back();
    }
  }, [state?.success, router]);

  return (
    <form action={formAction} className="space-y-4">
      <input type="hidden" name="id" value={taskId} />
      <div className="flex flex-col gap-2">
        <label htmlFor="title" className="text-sm font-medium">Task Title</label>
        <input 
          id="title"
          name="title" 
          defaultValue={initialData} 
          className="border p-2 rounded-md"
        />
      </div>
      <Button type="submit" disabled={isPending}>
        {isPending ? "Sauvegarde..." : "Enregistrer les modifications"}
      </Button>
    </form>
  );
}

À l'intérieur de votre Server Action, utilisez updateTag('tasks') pour expirer le cache immédiatement. Cela garantit que la prochaine requête attendra des données fraîches.

// actions/tasks.ts
"use server";

import { updateTag } from "next/cache";

export async function updateTaskAction(prevState: any, formData: FormData) {
  // ... logique de base de données ...
  
  // updateTag est conçu pour une cohérence immédiate dans les actions
  updateTag("tasks");
  
  return { success: true };
}

Pièges courants

  • Utiliser des Groupes de Routes au lieu d'Intercepteurs : Les noms de dossiers comme (modal) sont des groupes de routes. Vous devez utiliser (.), (..), ou (...) pour indiquer au routeur d'intercepter la navigation.
  • Oublier le fallback default.tsx : Next.js 16 l'exige pour les slots parallèles sans correspondance lors des chargements forcés ; son omission entraîne des erreurs de build.
  • revalidateTag avec un seul argument : Cette signature est obsolète dans Next.js 16. Utilisez revalidateTag(tag, "max") pour le SWR ou updateTag(tag) pour une invalidation immédiate dans les actions.
  • Oubli de defaultOpen={true} : Sans cela, les dialogues basés sur Radix se montent mais restent invisibles, bloquant l'interface d'arrière-plan.

FAQ

Qu'est-ce qu'une route interceptée dans Next.js ?

Une route interceptée vous permet de charger du contenu d'une autre partie de votre application dans le layout actuel tout en préservant le contexte. Les conventions comme (.) ou (..) définissent le nombre de segments à remonter dans la hiérarchie pour la correspondance.

Comment fermer une modale dans les routes interceptées Next.js ?

Utilisez router.back() de next/navigation. Puisque la modale est liée à un segment de route, reculer dans la pile d'historique démonte le slot et restaure l'URL précédente.

Quel est le but de default.tsx ?

Le fichier default.tsx sert de solution de repli pour les slots de routes parallèles lors d'une navigation forcée (comme un rafraîchissement de page) lorsque Next.js ne peut pas déterminer l'état actif de ce slot.

Quand utiliser updateTag vs revalidateTag ?

Utilisez updateTag(tag) à l'intérieur des Server Actions pour une cohérence immédiate (lecture après écriture). Utilisez revalidateTag(tag, "max") pour les mises à jour en arrière-plan où servir temporairement du contenu obsolète est acceptable pour la performance.

Conclusion

Maîtriser les modales pilotées par l'URL transforme la façon dont les utilisateurs interagissent avec vos tableaux de bord. En combinant les slots @modal, les conventions d'interception et le parcours programmatique de l'historique, vous offrez une capacité de lien profond sans sacrifier la fluidité d'une application mono-page.

Si vous souhaitez éviter le code répétitif et livrer des interfaces client plus rapidement, explorez nos modèles de tableaux de bord prêts pour la production chez Aniq UI. Ils sont livrés préconfigurés avec les architectures de routage Next.js 16, les formulaires React 19 et des systèmes de conception robustes dès le départ.

Cet article vous a été utile?

Partager:
world map

Communauté Mondiale d'Utilisateurs

Rejoignez des milliers de développeurs à travers le monde qui font confiance à Aniq-UI pour leurs projets. Nos modèles sont utilisés partout dans le monde pour créer des expériences web impressionnantes.

Nous Contacter

Need custom work or reskin? Get in touch with us

Aniq-uiAniq-uiAniq-ui