Amélioration de l'expérience administrateur : UI / UX avec ErrorBoundary, 404 et dashboard
Apprends à offrir une UX admin SaaS avec React Router 7: ErrorBoundary dédié, routes catch-all, sidebar active et dashboard synthétique. Level up dev.
Objectif : offrir une UX admin digne d’un SaaS
Jusqu’ici l’admin tombait :
- sur une page d’accueil vide,
- sur des routes incomplètes (pas de 404 contextualisée, sidebar absente en cas d’erreur),
- sur des menus qui ne reflétaient plus la structure réelle (Users vs Customers).
Dans cette itération nous :
- ajoutons un
ErrorBoundarydédié au layout admin ; - créons une route catch-all pour les URLs inconnues côté admin et public ;
- fusionnons les pages Customers / Users et simplifions la navigation ;
- indiquons le lien actif dans la sidebar ;
- mettons en place un dashboard synthétique, plus un bouton Logout.
1 ErrorBoundary spécifique au back-office
Pourquoi ? Conserver la sidebar même lorsqu’une route interne lève une erreur.
1export function ErrorBoundary() {2return (3<SidebarProvider>4<AdminSidebar /> // ✅ sidebar toujours visible5<SidebarInset>6<header className="flex h-16 items-center gap-2 border-b px-4">7<SidebarTrigger className="-ml-1" />8<h1 className="text-xl font-semibold text-destructive">9Administration – Erreur10</h1>11</header>12<div className="flex flex-1 flex-col gap-4 p-4">13<GeneralErrorBoundary14defaultStatusHandler={({ error }) => (15<AdminErrorCard error={error} /> // composant décrit plus bas16)}17/* statusHandlers 404, 403, fallback … */18/>19</div>20</SidebarInset>21</SidebarProvider>22)23}
Explication
- On enveloppe l’ancien
GeneralErrorBoundarypour garder la navigation latérale. - Les
statusHandlerspersonnalisent le rendu (404 → liens utiles, 403 → message d’accès refusé, etc.).
2 Route catch-all admin
Objectif : intercepter toute URL inconnue sous /admin/* et afficher
un message contextuel.
1export async function loader({ request }: Route.LoaderArgs) {2await requireAdmin(request) // sécurité = encore obligatoire3throw data("Page d'administration introuvable", { status: 404 })4}56export function ErrorBoundary() {7return <AdminNotFound /> // carte 404 illustrée8}
$.tsx(« dollar » dans le file-based routing) = catch-all.- On réutilise le composant
AdminNotFound(liens rapides vers produits, users…).
3 Route catch-all publique
Même principe côté front pour conserver <Navbar /> et <Footer /> en cas
d’erreur :
1export function ErrorBoundary() {2return <PublicNotFound /> // 404 full-width + CTA3}
4 Fusion Users + Customers
L’ancienne arborescence :
1/admin/users – Users (admin)2/admin/customers – Customers (doublon)
Action réalisée :
- suppression du dossier
customers+; - renommage de
users+/index.tsxenusers+/index.tsx(mais affichant désormais la liste clients) ; - le layout intermédiaire (ancien
users+/_layout) a été supprimé ; on ouvre la page détail dans un nouvel onglet pour un écran « plein » plus lisible.
Effet immédiat : moins de redirections, URL plus courtes :
/admin/users/:userId.
5 Lien actif dans la sidebar
1const isActive = location.pathname.startsWith(link.to)2…3<SidebarMenuButton asChild isActive={isActive}>4<Link to={link.to}>5<IconComponent className="size-4" />6<span>{link.label}</span>7</Link>8</SidebarMenuButton>
- Sur mobile comme desktop, le segment actif est mis en surbrillance.
startsWithcouvre aussi les routes imbriquées (/admin/users/123active/admin/users).
6 Dashboard synthétique
1export async function loader({ request }) {2await requireAdmin(request)3const stats = await getDashboardStats() // Prisma + agrégations4return { stats }5}
1<div className="grid gap-4 md:grid-cols-4">2<KpiCard title="Total utilisateurs" icon={Users} value={stats.totals.users} />3<KpiCard title="Total produits" icon={Package} value={stats.totals.products} />4<KpiCard title="Total commandes" icon={ShoppingCart} value={stats.totals.orders} />5<KpiCard title="Chiffre d'affaires" icon={Euro} value={`${stats.totals.revenue.toLocaleString('fr-FR')}€`} />6</div>
getDashboardStats() calcule :
totals.users,products,orders,revenue;ordersByStatus(PENDING, PAID, FULFILLED) ;recentOrders(5 dernières) ;recentUsers.
7 Bouton Logout interne à l’admin
1<SidebarMenuItem>2<SidebarMenuButton onClick={handleSignOut} className="text-red-600">3<LogOut className="size-4" />4<span>Déconnexion</span>5</SidebarMenuButton>6</SidebarMenuItem>
handleSignOut appelle signOut() fourni par Better Auth, puis redirige vers /.
8 Résultat
- page 404 admin et publique gardent leurs layouts respectifs ;
- liste clients unifiée, fiche détail plein écran + sessions actives ;
- dashboard synthétique visible au premier coup d’œil ;
- sidebar toujours lisible et active ;
- UX admin fluide : aucune page blanche, navigation plus claire.
Tip
9 À retenir
- ErrorBoundary local → garde la navigation en cas de panne.
- Route catch-all dans chaque layout pour éviter les 404 globales.
- Fusionner les pages réduit la dette UI et le nombre de liens.
- Lien actif : un feedback visuel très simple améliore grandement l’orientation.
- Un tableau de bord condensé vaut mieux qu’une page blanche.
10 Commit final
1git checkout -b 7-15-amelioration-experience-admin2git add .3git commit -m "feat(admin): UX améliorée (ErrorBoundary, 404, sidebar active, dashboard)"4git push -u origin 7-15-amelioration-experience-admin
Ta branche est prête pour la PR – la CI passera sans toucher au backend 🚀
Comprendre les concepts fondamentaux
Quelle est la principale différence entre les composants client et serveur dans React ?
Optimisation des performances
Quelle technique est recommandée pour éviter les rendus inutiles dans React ?
Architecture des données
Quel hook permet de gérer les effets de bord dans un composant React ?