Ajout d’un back-office admin avec React Router 7

Crée un back-office admin efficace en React Router 7 : routing imbriqué, gestion produits, validation Zod et interface épurée.

4 min read
Déverrouillez votre potentiel

avec React Router 7

Vous en avez marre de...

❌ perdre du temps à chercher des informations éparpillées
❌ ne pas avoir de retour sur votre progression
Assistant IA spécialisé

Posez vos questions 24/7 à notre IA experte en React Router 7

Quiz interactifs

Validez vos acquis avec des quiz personnalisés et un feedback instantané

9 modules
72 leçons
Accès à vie
299.49
-35%

Ajoute un back-office admin sans douleur

1 Préparer un layout dédié

Nous isolons l’interface d’administration dans un dossier admin+. L’underscore final + permet au file-based routing d’ajouter automatiquement le préfixe /admin à chaque page.

app/routes/admin+/_adminLayout.tsx
1
import { SidebarProvider, SidebarTrigger } from "~/components/ui/sidebar"
2
import { AdminSidebar } from "./_adminLayout"
3
4
export default function AdminLayout() {
5
return (
6
<SidebarProvider>
7
<AdminSidebar /> {/* sidebar Shadcn – 100 % responsive */}
8
<SidebarTrigger className="-ml-1" />
9
10
{/* contenu injecté par les routes enfants */}
11
<main className="flex flex-1 flex-col gap-4 p-4">
12
<Outlet />
13
</main>
14
</SidebarProvider>
15
)
16
}

Pourquoi ?

  1. La barre latérale reste visible sur toutes les pages admin.
  2. Le composant <Outlet /> affiche la page active (routing imbriqué).

2 Déclarer la liste des produits

Une page index affiche le tableau :

app/routes/admin+/products.index.tsx
1
export async function loader() {
2
return { products: await adminGetProducts() }
3
}
4
5
export default function ProductsPage() {
6
const { products } = useLoaderData<typeof loader>()
7
return (
8
<DataTable
9
columns={adminProductsColumns}
10
data={products}
11
searchPlaceholder="Rechercher..."
12
/>
13
)
14
}
  • loader() récupère tous les produits en base via Prisma.
  • DataTable vient de shadcn/ui : pagination, tri et filtre déjà inclus.

3 Colonnes dynamiques et actions

app/routes/admin+/admin-products-columns.tsx
1
export const adminProductsColumns: ColumnDef<Product>[] = [
2
{ accessorKey: "name", header: "Nom", cell: NameCell },
3
{ accessorKey: "priceCents", header: "Prix", cell: PriceCell },
4
{ accessorKey: "stock", header: "Stock", cell: StockBadge },
5
{
6
accessorKey: "isActive",
7
header: "Statut",
8
cell: ({ row }) => <ProductStatusToggle product={row.original} />,
9
},
10
{ id: "actions", header: "Actions", cell: ActionsCell },
11
]

Chaque colonne est typée – l’autocomplétion évite les fautes de clé.

3.1 Activer / désactiver un produit

{9,15} app/routes/admin+/admin-products-columns.tsx
1
const ProductStatusToggle = ({ product }: { product: Product }) => {
2
const fetcher = useFetcher()
3
const [form, fields] = useForm({
4
constraint: getZodConstraint(ToggleStatusSchema),
5
onValidate: ({ formData }) =>
6
parseWithZod(formData, { schema: ToggleStatusSchema }),
7
})
8
9
return (
10
<fetcher.Form method="POST" {...getFormProps(form)} style={{ display:"contents" }}>
11
<input {...getInputProps(fields.intent, { type:"hidden" })} value="toggle-status" />
12
<input {...getInputProps(fields.productSlug,{ type:"hidden" })} value={product.slug} />
13
<Switch
14
checked={product.isActive}
15
onClick={(e) => fetcher.submit(e.currentTarget.form)}
16
/>
17
</fetcher.Form>
18
)
19
}
  • useFetcher() déclenche l’action sans navigation.
  • La validation ToggleStatusSchema (Zod) garantit que productSlug existe.

3.2 Suppression protégée

Un AlertDialog confirme l’intention, puis :

{4} app/routes/admin+/admin-products-columns.tsx
1
formData.set("intent", "delete-product")

Le même action() gère les deux cas grâce à une Discriminated Union :

{3,8} app/routes/admin+/products.index.tsx
1
export const ActionSchema = z.discriminatedUnion("intent", [
2
ToggleStatusSchema,
3
DeleteProductSchema,
4
])
5
6
switch (submission.value.intent) {
7
case "toggle-status": await toggleProductStatus(slug); break
8
case "delete-product": await deleteProduct(slug); break
9
}

4 Créer / éditer un produit

Route dynamique products.$productSlug.tsx.

app/routes/admin+/products.$productSlug.tsx
1
export const ProductSchema = z.object({
2
name: z.string().min(1),
3
slug: z.string().min(1),
4
description: z.string().optional(),
5
content: z.string().optional(),
6
priceCents: z.coerce.number().min(0),
7
currency: z.string().default("EUR"),
8
stock: z.coerce.number().min(0),
9
isActive: z.boolean().default(false),
10
})
  • Même schéma pour création et édition.
  • isSlugTaken() vérifie l’unicité avant d’accepter la requête.

Le formulaire Conform réutilise les <Field> de l’Epic Stack :

{14-18} app/routes/admin+/products.$productSlug.tsx
1
<Field
2
labelProps={{ children:"Slug" }}
3
inputProps={{
4
...getInputProps(fields.slug,{ type:"text" }),
5
placeholder:"nouveau-robot-ultra-pro",
6
}}
7
errors={fields.slug.errors}
8
/>

Après validation :

  • createProduct() insère en BDD puis redirige vers le slug créé.
  • updateProduct() renvoie hasUpdatedSlug → redirection si besoin.

5 Séparer layout public et admin

En déplaçant Navbar + Footer dans _publicLayout.tsx :

app/routes/_public+/_publicLayout.tsx
1
<Navbar />
2
<main className="flex-1">
3
<Outlet />
4
</main>
5
<Footer />

Le back-office reste épuré (pas de barre publique dans /admin).

6 Ce que tu gagnes

  • Interface admin complète en une seule branche.
  • Code lisible : Zod + Conform gèrent la validation client/serveur sans useState.
  • Sidebar Shadcn : responsive et réutilisable.
  • Actions isolées par useFetcher() : pas de rechargement global.

La base pour gérer produits, stock et statut est posée : tu es prêt pour ajouter la gestion des images et l’authentification admin !

Premium
Quiz interactif
Testez vos connaissances et validez votre compréhension du module avec notre quiz interactif.
1

Comprendre les concepts fondamentaux

Quelle est la principale différence entre les composants client et serveur dans React ?

Les composants client s'exécutent uniquement dans le navigateur
Les composants serveur peuvent utiliser useState
Les composants client sont plus rapides
Il n'y a aucune différence significative
2

Optimisation des performances

Quelle technique est recommandée pour éviter les rendus inutiles dans React ?

Utiliser React.memo pour les composants fonctionnels
Ajouter plus d'états locaux
Éviter d'utiliser les props
Toujours utiliser les class components
3

Architecture des données

Quel hook permet de gérer les effets de bord dans un composant React ?

useEffect
useState
useMemo
useContext
4

Gestion des erreurs

Comment implémenter la gestion des erreurs pour les requêtes API dans React ?

Utiliser try/catch avec async/await
Ignorer les erreurs
Toujours afficher un message d'erreur
Rediriger l'utilisateur
5

Déploiement et CI/CD

Quelle est la meilleure pratique pour déployer une application React en production ?

Utiliser un service CI/CD comme GitHub Actions
Copier les fichiers manuellement via FTP
Envoyer le code source complet
Ne jamais mettre à jour l'application

Débloquez ce quiz et tous les autres contenus premium en achetant ce cours