Back-office Next.js : guide complet RBAC de la conception à l'implémentation

Vous fixez l’écran : le 23e if (user.role === 'admin').
L’année dernière, j’ai repris un back-office laissé par un collègue — les contrôles d’accès éparpillés dans plus de 20 fichiers. Chaque nouveau rôle impliquait une recherche globale. Une fois, un oubli : un utilisateur standard a vu les rapports financiers. Appel à 2 h du matin pour corriger le bug.
J’ai passé en revue toutes les approches Next.js pour les back-offices : tout le monde souffre avec les permissions. On sait qu’il faut du RBAC, mais comment modéliser les tables ? Comment écrire le middleware ? Comment générer les menus dynamiques ? Ant Design ou shadcn/ui pour les tableaux ? Pas de réponse standard — les pièges sont inévitables.
Deux semaines de refactorisation plus tard, je dormais enfin tranquille. Cet article condense cette expérience — de la conception RBAC au middleware Next.js 15, en passant par les menus dynamiques et le choix des composants de tableau. Tout le plan est ici.
Conception du modèle RBAC (pourquoi cette approche)
Qu’est-ce que le RBAC, et pourquoi tout le monde l’utilise
RBAC signifie Role-Based Access Control (contrôle d’accès basé sur les rôles). L’idée est simple : utilisateur → rôle → permission → ressource.
Pourquoi ne pas attribuer les permissions directement aux utilisateurs ? C’est possible, mais pénible.
Imaginez 5 nouveaux agents support : en attribution directe, vous configurez pour chacun consulter les commandes, répondre aux commentaires, exporter les rapports… cinq fois le même travail. Avec le RBAC, vous créez un rôle « Support », y attachez les permissions, puis assignez le rôle aux nouveaux arrivants. Une config, réutilisable à l’infini.
Surtout le coût de maintenance. Le product manager dit : « Les agents ne peuvent plus exporter les rapports, données trop sensibles. » En RBAC, vous modifiez le rôle une fois — tous les agents sont synchronisés. En attribution directe ? Un par un ; un oubli, et c’est l’incident en production.
Plus de 80 % des SaaS entreprise à l’étranger utilisent le RBAC ou une variante. Raison concrète : flexibilité et maintenabilité. Plus simple que l’ABAC (contrôle basé sur les attributs), plus souple que l’attribution directe utilisateur-permission.
Granularité des permissions sans s’épuiser
La granularité, c’est un art. Trop grossier : contrôle insuffisant. Trop fin : coût de maintenance explosif.
Mon approche en trois niveaux :
Permissions au niveau page (routage)
- Le plus basique : l’utilisateur peut-il accéder à une page ?
- Ex. :
/admin/usersréservé aux administrateurs - Via le middleware Next.js — détaillé plus loin
Permissions au niveau module (menu)
- Contrôle les entrées de la barre latérale
- L’utilisateur ne voit pas ce qu’il ne peut pas ouvrir — meilleure UX
- Filtrage dynamique côté frontend selon la config menu
Permissions au niveau action (boutons)
- Jusqu’au bouton précis
- Ex. : « Supprimer l’utilisateur » visible uniquement pour le super-admin
- À utiliser avec parcimonie — pas tous les boutons n’en ont besoin
J’ai vu des équipes contrôler chaque colonne de chaque tableau. Résultat : config infernale, performances lamentables. Principe clé : ne pas sur-concevoir.
Pour le nommage, je recommande resource:action :
user:create— créer un utilisateurorder:delete— supprimer une commandereport:export— exporter un rapport
Clair, facile à trier et à rechercher.
Schéma de base de données
Quatre tables cœur : utilisateurs, rôles, permissions, ressources. Deux tables de liaison pour les relations N-N.
// 用户表
User {
id: string
name: string
email: string
// 其他用户信息
}
// 角色表
Role {
id: string
name: string // "管理员"、"客服"、"运营"
code: string // "admin"、"service"、"operator"
description: string
}
// 权限表
Permission {
id: string
name: string // "创建用户"
code: string // "user:create"
resource: string // "user"
action: string // "create"
}
// 资源表(可选,看业务复杂度)
Resource {
id: string
name: string // "用户管理"
code: string // "user"
type: string // "page" | "api" | "menu"
}
// 用户-角色关联表
UserRole {
userId: string
roleId: string
}
// 角色-权限关联表
RolePermission {
roleId: string
permissionId: string
}
Pourquoi ne pas mettre un roleId directement dans User ? Parce qu’un utilisateur peut avoir plusieurs rôles.
Zhang San est à la fois « Responsable technique » et « Modérateur de contenu » — les permissions se fusionnent. La table de liaison gère ça naturellement ; un JOIN suffit à la requête.
Si vous gérez aussi l’organigramme (départements, postes), ajoutez Department et Position. Mais ne créez pas toutes les tables d’un coup : étendre au besoin. Avec Prisma ou un ORM similaire, ajouter champs et tables plus tard reste simple.
Middleware Next.js : protection des routes (cœur technique)
Pourquoi le middleware est indispensable
Au début, j’écrivais des vérifications dans chaque page :
// ❌ 反面教材
export default function UsersPage() {
const { user } = useSession()
if (!user) {
redirect('/login')
}
if (user.role !== 'admin') {
return <div>无权访问</div>
}
return <div>用户列表...</div>
}
Ça a l’air correct ? Problèmes :
- Répéter sur chaque page — épuisant
- Oublier une page — faille de sécurité
- La page se rend avant le contrôle — flash visible
- Encore plus pénible en SSR
Le middleware Next.js règle tout ça. Il s’exécute avant la page : interception et traitement unifiés. Performant, code propre, maintenance facile.
Implémentation complète de middleware.ts
Sous Next.js 15, le middleware va à la racine du projet. J’utilise NextAuth ; Clerk ou autre convient aussi.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { getToken } from 'next-auth/jwt'
// 定义路由权限映射
const ROUTE_PERMISSIONS = {
'/admin': ['admin'], // 只有 admin 角色能访问
'/admin/users': ['admin', 'operator'], // admin 和 operator 都能访问
'/dashboard': ['admin', 'operator', 'viewer'], // 三种角色都能访问
'/reports': ['admin'],
} as const
// 公开路由,不需要登录
const PUBLIC_ROUTES = ['/login', '/register', '/forgot-password']
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
// 1. 公开路由直接放行
if (PUBLIC_ROUTES.includes(pathname)) {
return NextResponse.next()
}
// 2. 获取用户会话
const token = await getToken({
req: request,
secret: process.env.NEXTAUTH_SECRET,
})
// 3. 未登录,重定向到登录页
if (!token) {
const loginUrl = new URL('/login', request.url)
loginUrl.searchParams.set('from', pathname) // 记录来源,登录后跳回
return NextResponse.redirect(loginUrl)
}
// 4. 检查路由权限
const userRole = token.role as string
const requiredRoles = ROUTE_PERMISSIONS[pathname as keyof typeof ROUTE_PERMISSIONS]
if (requiredRoles && !requiredRoles.includes(userRole)) {
// 权限不足,返回 403
return NextResponse.rewrite(new URL('/403', request.url))
}
// 5. 权限验证通过,继续请求
return NextResponse.next()
}
// 配置中间件匹配规则
export const config = {
matcher: [
// 匹配所有路由,除了静态文件和 API 路由(根据需要调整)
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
}
Points clés :
Mapping route-permission : constante objet, lisible. Nouvelle route → une ligne ici, pas de chasse au code.
Routes publiques : login, inscription, etc. listées à part — évite la boucle (l’utilisateur veut se connecter, le middleware le renvoie au login…).
Origine de connexion : loginUrl.searchParams.set('from', pathname) — après login, retour à /admin/users, pas à l’accueil.
Accès refusé : NextResponse.rewrite plutôt que redirect — l’URL ne change pas, contenu 403. Redirection vers une page dédiée possible aussi.
Coordination frontend / backend
Point crucial : le middleware n’est que la première ligne ; l’API backend doit revérifier.
Les contrôles frontend optimisent l’expérience utilisateur. Le code navigateur est modifiable — outils dev, contournement facile. La vraie défense est côté serveur.
Dans les Server Actions et routes API Next.js, revérifiez systématiquement :
// app/actions/deleteUser.ts
'use server'
import { auth } from '@/lib/auth'
import { db } from '@/lib/db'
export async function deleteUser(userId: string) {
// 再次验证用户权限
const session = await auth()
if (!session || session.user.role !== 'admin') {
throw new Error('无权执行此操作')
}
// 执行删除
await db.user.delete({ where: { id: userId } })
return { success: true }
}
Double sécurité :
- Middleware frontend : retour rapide, pas de page interdite affichée
- Validation backend : vraie barrière contre les requêtes malveillantes
Certaines équipes extraient la config permissions dans un module partagé — monorepo idéal pour garder les règles alignées.
Performance : où stocker les permissions
Interroger la BDD à chaque requête ? Non — trop lent.
Deux approches :
Option 1 : encoder dans le JWT
// NextAuth callbacks
callbacks: {
async jwt({ token, user }) {
if (user) {
token.role = user.role
token.permissions = user.permissions // 直接带上权限列表
}
return token
}
}
Avantage : pas de requête BDD dans le middleware. Inconvénient : changement de permission effectif à l’expiration du token. Adapté si les permissions changent peu.
Option 2 : cache Redis
Permissions changeantes souvent → cache Redis, lecture middleware. Rapide, quasi temps réel, une dépendance de plus.
Mon projet : option 1, token 1 h. Après modification admin, on demande une reconnexion. Les changements de permissions restent rares.
Menus dynamiques et permissions (clé UX)
Structure de configuration du menu
Le cœur : filtrer les entrées selon les permissions. D’abord une config complète, puis filtrage dynamique.
// config/menu.ts
import { Home, Users, Settings, FileText } from 'lucide-react'
export interface MenuItem {
key: string
label: string
icon: React.ComponentType
path?: string
permission?: string // 需要的权限
children?: MenuItem[]
}
export const MENU_CONFIG: MenuItem[] = [
{
key: 'dashboard',
label: '仪表盘',
icon: Home,
path: '/dashboard',
// 不设置 permission 表示所有登录用户都能看
},
{
key: 'users',
label: '用户管理',
icon: Users,
permission: 'user:read', // 需要 user:read 权限
children: [
{
key: 'users-list',
label: '用户列表',
path: '/admin/users',
permission: 'user:read',
},
{
key: 'users-roles',
label: '角色管理',
path: '/admin/roles',
permission: 'role:read',
},
],
},
{
key: 'reports',
label: '报表中心',
icon: FileText,
path: '/reports',
permission: 'report:read',
},
{
key: 'settings',
label: '系统设置',
icon: Settings,
path: '/settings',
permission: 'system:config',
},
]
Points :
Arbre ou plat ? J’ai choisi l’arbre — relations claires, rendu récursif. Le plat avec parentKey a aussi ses partisans.
permission optionnel : sans permission, visible pour tout utilisateur connecté (ex. tableau de bord).
Icônes en composants : import direct depuis lucide-react — typage et rendu propres.
Algorithme de filtrage
Piège classique : parent sans permission, enfant avec — afficher ou masquer « Gestion utilisateurs » ?
Ma règle : si au moins un enfant est visible, le parent reste visible.
// lib/menu.ts
export function filterMenuByPermissions(
menuItems: MenuItem[],
userPermissions: string[]
): MenuItem[] {
return menuItems
.map((item) => {
// 处理子菜单
const filteredChildren = item.children
? filterMenuByPermissions(item.children, userPermissions)
: undefined
// 判断当前项是否可见
const hasPermission =
!item.permission || userPermissions.includes(item.permission)
const hasVisibleChildren =
filteredChildren && filteredChildren.length > 0
// 无权限且没有可见子菜单,过滤掉
if (!hasPermission && !hasVisibleChildren) {
return null
}
// 返回过滤后的菜单项
return {
...item,
children: filteredChildren,
}
})
.filter((item): item is MenuItem => item !== null)
}
Récursion claire. Quelques dizaines d’entrées max — performances OK.
Utilisation dans les composants
Hook React dédié :
// hooks/usePermissionMenu.ts
'use client'
import { useMemo } from 'react'
import { useSession } from 'next-auth/react'
import { filterMenuByPermissions } from '@/lib/menu'
import { MENU_CONFIG } from '@/config/menu'
export function usePermissionMenu() {
const { data: session } = useSession()
const filteredMenu = useMemo(() => {
if (!session?.user?.permissions) {
return []
}
return filterMenuByPermissions(MENU_CONFIG, session.user.permissions)
}, [session?.user?.permissions])
return filteredMenu
}
useMemo évite de recalculer à chaque rendu si les permissions sont stables.
Dans la sidebar :
// components/Sidebar.tsx
'use client'
import { usePermissionMenu } from '@/hooks/usePermissionMenu'
export function Sidebar() {
const menu = usePermissionMenu()
return (
<nav>
{menu.map((item) => (
<MenuItem key={item.key} item={item} />
))}
</nav>
)
}
Propre et direct.
Surbrillance de route et fil d’Ariane
Deux détails : route active et fil d’Ariane.
Surbrillance via pathname :
'use client'
import { usePathname } from 'next/navigation'
function MenuItem({ item }: { item: MenuItem }) {
const pathname = usePathname()
const isActive = item.path === pathname
return (
<Link
href={item.path || '#'}
className={isActive ? 'bg-blue-100 text-blue-600' : 'text-gray-700'}
>
<item.icon />
{item.label}
</Link>
)
}
Fil d’Ariane : trouver le chemin menu pour la route courante :
// lib/menu.ts
export function getMenuPath(
menuItems: MenuItem[],
targetPath: string,
path: MenuItem[] = []
): MenuItem[] | null {
for (const item of menuItems) {
const currentPath = [...path, item]
if (item.path === targetPath) {
return currentPath
}
if (item.children) {
const result = getMenuPath(item.children, targetPath, currentPath)
if (result) return result
}
}
return null
}
Récursion jusqu’à la racine — le composant breadcrumb consomme ce chemin.
Routes dynamiques (/admin/users/123) : adapter le matching en retirant les segments variables.
Tableaux de données : choix et mise en pratique
Comparaison des solutions en 2026
Un back-office sans tableaux ? Impossible. Listes utilisateurs, commandes, logs…
J’ai testé les options courantes :
Ant Design Table
- Avantages : complet, doc riche. Tri, filtres, pagination, lignes extensibles, colonnes fixes.
- Inconvénients : personnalisation CSS difficile, gros bundle, style imposé.
- Pour : back-offices traditionnels, équipes habituées à Ant Design.
MUI DataGrid
- Avantages : Material Design, fonctions entreprise (scroll virtuel, réordonnancement colonnes).
- Inconvénients : fonctions avancées payantes (Pro), courbe d’apprentissage, surcharge CSS complexe.
- Pour : grands projets avec budget.
shadcn/ui + TanStack Table
- Avantages : sans style imposé, très personnalisable, TypeScript natif, excellentes perfs. Composants maîtrisés, import à la demande.
- Inconvénients : styles et UI à écrire — investissement initial.
- Pour : projets modernes, flexibilité et performance, équipes qui codent.
React-Admin
- Avantages : tout-en-un CRUD + permissions, prêt à l’emploi.
- Inconvénients : couplage framework, personnalisation limitée.
- Pour : prototypes rapides, CRUD standardisé.
Mon choix : shadcn/ui + TanStack Table. Tailwind CSS dans le projet, intégration fluide, styles sous contrôle. API TanStack Table excellente — logique séparée de l’UI.
Implémentation shadcn/ui en détail
Le Data Table shadcn/ui n’est pas un composant clé en main — c’est un guide de montage. Cœur : TanStack Table ; shadcn/ui fournit les composants Table de base.
Installation :
npx shadcn@latest add table
npm install @tanstack/react-table
Créez un composant DataTable (code complet en tête d’article).
Usage : définir les colonnes :
// app/admin/users/page.tsx
'use client'
import { ColumnDef } from '@tanstack/react-table'
import { DataTable } from '@/components/DataTable'
import { Button } from '@/components/ui/button'
import { usePermission } from '@/hooks/usePermission'
interface User {
id: string
name: string
email: string
role: string
}
const columns: ColumnDef<User>[] = [
{
accessorKey: 'name',
header: '姓名',
},
{
accessorKey: 'email',
header: '邮箱',
},
{
accessorKey: 'role',
header: '角色',
},
{
id: 'actions',
cell: ({ row }) => {
const user = row.original
const { hasPermission } = usePermission()
return (
<div className="flex gap-2">
{hasPermission('user:update') && (
<Button size="sm" variant="outline">
编辑
</Button>
)}
{hasPermission('user:delete') && (
<Button size="sm" variant="destructive">
删除
</Button>
)}
</div>
)
},
},
]
export default function UsersPage() {
// 实际项目中这里应该是异步获取数据
const users: User[] = [
{ id: '1', name: '张三', email: '[email protected]', role: 'admin' },
{ id: '2', name: '李四', email: '[email protected]', role: 'user' },
]
return (
<div className="container mx-auto py-10">
<DataTable columns={columns} data={users} />
</div>
)
}
La colonne actions utilise usePermission — boutons différents selon les droits.
Pagination et filtres côté serveur
L’exemple précédent pagine côté client. À grande échelle, pagination serveur :
// app/api/users/route.ts
import { NextRequest } from 'next/server'
import { db } from '@/lib/db'
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams
const page = parseInt(searchParams.get('page') || '0')
const size = parseInt(searchParams.get('size') || '10')
const [data, total] = await Promise.all([
db.user.findMany({
skip: page * size,
take: size,
}),
db.user.count(),
])
return Response.json({ data, total })
}
N’oubliez pas la validation des permissions — voir le chapitre middleware.
Bonnes pratiques de permissions dans les tableaux
Deux niveaux :
Colonnes sensibles : certaines colonnes réservées à certains rôles (téléphone, numéro d’identité)
const columns: ColumnDef<User>[] = [
{
accessorKey: 'name',
header: '姓名',
},
// 只有 admin 能看到敏感信息列
...(hasPermission('user:view-sensitive')
? [
{
accessorKey: 'phone',
header: '手机号',
},
]
: []),
]
Actions : boutons conditionnés par usePermission (exemple ci-dessus).
Encapsulez la logique dans un hook réutilisable :
// hooks/usePermission.ts
'use client'
import { useSession } from 'next-auth/react'
export function usePermission() {
const { data: session } = useSession()
const hasPermission = (permission: string) => {
return session?.user?.permissions?.includes(permission) ?? false
}
const hasAnyPermission = (permissions: string[]) => {
return permissions.some((p) => hasPermission(p))
}
const hasAllPermissions = (permissions: string[]) => {
return permissions.every((p) => hasPermission(p))
}
return { hasPermission, hasAnyPermission, hasAllPermissions }
}
Usage uniforme dans les composants.
Production : pièges et bonnes pratiques
Erreurs courantes et anti-patterns
Mes écueils — évitez-les :
❌ Erreur 1 : permissions uniquement frontend
Le plus dangereux. Code exécuté dans le navigateur — DevTools, modification de role: 'user' en role: 'admin'.
Un analyste concurrent s’est inscrit, a modifié son rôle, et a consulté notre back-office toute une nuit. Le product manager n’était pas content le lendemain.
✅ Correct : frontend = UX ; API backend revérifie. Chaque action sensible dans Server Actions ou routes API.
❌ Erreur 2 : if (user.role === 'admin') partout
20 fichiers, nouveau rôle = cauchemar.
✅ Correct : config centralisée + usePermission (voir plus haut).
❌ Erreur 3 : permissions en dur
// 反面教材
const ADMIN_USERS = ['[email protected]', '[email protected]']
if (ADMIN_USERS.includes(user.email)) {
// 管理员逻辑
}
Le boss change d’email → redeploy. Absurde.
✅ Correct : permissions en BDD, rôles configurables, pas de listes hardcodées.
Optimisations performance
Mal conçu, le système de permissions ralentit tout :
1. Permissions dans le token
Rôles et permissions dans le JWT — pas de requête BDD par requête.
2. Cache du filtrage menu
Récursif mais léger — useMemo dans usePermissionMenu suffit.
3. Code splitting par route
App Router Next.js : un chunk par page. Back-office multi-pages sans ça = chargement initial lent.
4. Moins de vérifications redondantes
Page en lecture seule déjà protégée par le middleware — boutons internes : permissions incrémentales (supprimer, éditer) seulement.
Checklist sécurité avant mise en ligne
✅ API backend
- Server Actions avec contrôle de permission
- Routes API vérifiées
- Opérations sensibles avec double validation (ex. suppression utilisateur)
✅ Prévention de l’élévation de privilèges
- L’utilisateur ne modifie pas son propre rôle
- Pas d’auto-attribution de permissions
- Ressources haut niveau inaccessibles aux rôles bas
✅ Journal d’audit
- Opérations critiques journalisées (création, suppression, changement de permissions)
- Qui, quand, quoi
- Logs append-only
✅ Gestion de session
- Expiration token raisonnable (1 h recommandé)
- Déconnexion forcée (invalider toutes les sessions)
- Anciens tokens invalidés après changement de mot de passe
✅ Validation des entrées
- Frontend et backend
- Zod pour les schémas
- Injection SQL évitée avec Prisma/ORM
Monitoring et alertes
La mise en ligne n’est pas la fin :
Métriques :
- Pic de 403 → tentative de sonde
- Requêtes massives d’un même utilisateur → bot ou attaque
- Modifications de permissions fréquentes → config instable
Alertes :
- Actions super-admin en temps réel
- Email sur changement de config permissions
- SMS connexion anormale (lieu, horaire)
Sentry, DataDog, etc. couvrent ces cas.
Leçons tirées de la production
Cas 1 : menu et route désalignés
« Rapports financiers » visible pour les ops dans le menu, oublié dans le middleware — clic → 403. Une semaine de tickets avant la découverte.
Leçon : config unifiée menu + routes.
Cas 2 : cache JWT, révocation tardive
Permissions dans JWT, expiration 24 h. Révocation d’un utilisateur — accès maintenu jusqu’au lendemain.
Leçon : opérations sensibles → revérification BDD ou blacklist Redis des permissions révoquées.
Cas 3 : API sans contrôle
Frontend impeccable, une route API oubliée — Postman contourne tout.
Leçon : backend = dernière ligne ; middleware ou décorateur uniforme.
En résumé : frontend = UX, backend = sécurité. Les deux, mais le backend prime.
Conclusion
Le RBAC n’est pas sorcier — bien le faire, si.
De la conception RBAC au middleware Next.js 15, menus dynamiques et tableaux : l’essentiel d’un back-office sécurisé. Trois principes :
- Conception sobre : étendre au besoin, pas de modèle surdimensionné dès le jour 1
- Implémentation en couches : middleware, menu filtré, boutons conditionnels — chacun son rôle
- Double sécurité : frontend pour l’expérience, backend pour la protection
Pour démarrer :
- Quatre tables RBAC (utilisateur, rôle, permission, ressource)
- Middleware Next.js + config permissions en constantes
- Menu dynamique encapsulé en Hook
- Tableaux shadcn/ui + TanStack Table pour la flexibilité
Deux semaines de refactorisation — fatigantes mais rentables. Nouveau rôle : config BDD, zéro ligne de code. « Auditeur » ajouté en dix minutes.
Un bon système de permissions accélère toute l’équipe. N’attendez pas l’incident de sécurité.
Référence open source : HaloLight — Next.js 15 + React 19 + TypeScript + RBAC complet. Code de qualité, excellent pour apprendre.
Des questions en implémentation ? Commentez — j’ai probablement déjà marché dans ces pièges.
Implémentation RBAC pour back-office Next.js
Étapes complètes pour construire un système RBAC dans un back-office Next.js
⏱️ Estimated time: 120 min
- 1
Step 1: Étape 1 : concevoir le schéma RBAC
Créer quatre tables cœur et deux tables de liaison :
**Tables cœur** :
• User : informations utilisateur
• Role : définition des rôles (admin, operator, viewer, etc.)
• Permission : définitions (format resource:action, ex. user:create)
• Resource (optionnel) : définition des ressources
**Tables de liaison** :
• UserRole : relation N-N utilisateur-rôle
• RolePermission : relation N-N rôle-permission
**Convention de nommage** :
Format resource:action pour faciliter gestion et recherche.
**Extensibilité** :
Garder simple au départ ; ajouter Department et Position si besoin d'organigramme.
Utiliser Prisma ou un ORM pour faciliter les évolutions. - 2
Step 2: Étape 2 : middleware Next.js pour les routes
Créer middleware.ts à la racine :
**Mapping des permissions** :
• Constante ROUTE_PERMISSIONS
• Rôles requis par route
• PUBLIC_ROUTES (login, inscription, etc.)
**Logique middleware** :
1. Route publique → laisser passer
2. getToken pour la session
3. Non connecté → redirection login (enregistrer la page d'origine)
4. Vérifier le rôle vs permissions route
5. Insuffisant → page 403
**Performance** :
• Encoder permissions dans le JWT
• Éviter requête BDD à chaque requête
• Expiration token ~1 h
**Matcher** :
Exclure statiques et routes API ; valider les pages uniquement. - 3
Step 3: Étape 3 : menus dynamiques et filtrage
Config menu et logique de filtrage :
**Structure** (config/menu.ts) :
• Arbre de menus
• Champs key, label, icon, path, permission
• permission optionnel = visible pour tout utilisateur connecté
**Algorithme** (lib/menu.ts) :
• filterMenuByPermissions récursif
• Parent visible si enfant autorisé
• Retourner l'arbre filtré
**Hook** (hooks/usePermissionMenu.ts) :
• useSession pour les permissions
• useMemo pour le cache
• Pas de recalcul si permissions inchangées
**Surbrillance et fil d'Ariane** :
• usePathname pour la route active
• getMenuPath pour le breadcrumb
• Gérer les routes dynamiques - 4
Step 4: Étape 4 : shadcn/ui + TanStack Table
Composant tableau réutilisable :
**Installation** :
• npx shadcn@latest add table
• npm install @tanstack/react-table
**DataTable** :
• useReactTable de TanStack
• Tri, pagination, filtres de base
• Typage TypeScript
**Permissions tableau** :
• Colonnes sensibles : rendu conditionnel
• Actions : usePermission pour les boutons
• hasPermission, hasAnyPermission, hasAllPermissions
**Pagination serveur** :
• Paramètres page et size en API
• Prisma skip/take
• Retour data + total
**Rappel** :
Contrôles frontend = UX ; l'API doit revérifier. - 5
Step 5: Étape 5 : validation API et Server Actions
Renforcer la défense backend :
**Server Actions** :
• auth() en tête de chaque action
• Vérifier rôle et permissions
• Erreur si insuffisant
**Routes API** :
• getToken pour l'utilisateur
• Valider la légitimité
• Double validation pour opérations sensibles
**Config unifiée** :
• Module permissions partagé
• Même source frontend/backend
• Idéal en monorepo
**Audit** :
• Journaliser création, suppression, changements de permissions
• Opérateur, horodatage, contenu
• Logs non modifiables - 6
Step 6: Étape 6 : performance et durcissement sécurité
Optimisations production :
**Performance** :
• Permissions dans JWT
• useMemo pour menus
• Code splitting par route
• Éviter vérifications redondantes
**Checklist sécurité** :
• Toutes les API vérifiées
• Prévention élévation de privilèges
• Expiration token raisonnable
• Validation entrées frontend/backend
• Schémas Zod
**Monitoring** :
• Surveiller les 403
• Détecter requêtes anormales
• Alertes super-admin
• Email sur changements de config
**Pièges à éviter** :
• Permissions frontend seules
• Config en dur
• Menu et routes incohérents
• Opérations sensibles basées uniquement sur le cache token
FAQ
Pourquoi le RBAC plutôt que l'attribution directe des permissions ?
• **Gestion groupée** : 5 nouveaux agents = un rôle, pas 5 configs
• **Mise à jour unifiée** : modifier le rôle synchronise tous les utilisateurs
• **Extensible** : plusieurs rôles par utilisateur, permissions fusionnées
• **Moins d'erreurs** : attribution directe = oublis et failles
Plus de 80 % des SaaS entreprise utilisent le RBAC pour l'équilibre flexibilité/maintenabilité.
Middleware Next.js vs contrôle en composant : quelle différence ?
**Middleware (recommandé)** :
• Avant le rendu de la page, interception unifiée
• 60-80 % plus rapide qu'en composant
• Code centralisé, moins d'oublis
• Compatible SSR
**Composant** :
• Après rendu, flash possible
• Répétition par page, risque d'oubli
• Coût de maintenance élevé
Le middleware n'est que la première ligne — l'API backend doit revérifier !
Menu dynamique : parent sans permission, enfant avec ?
**Logique d'affichage** :
• Au moins un enfant visible → parent affiché
• L'utilisateur accède aux sous-menus autorisés
**Implémentation** :
Algorithme récursif :
1. Filtrer les enfants
2. Vérifier permission de l'entrée courante
3. Sans permission mais enfants visibles → conserver
4. Sans permission ni enfant → supprimer
Strict côté sécurité, fluide côté UX.
shadcn/ui + TanStack Table ou Ant Design Table ?
**Ant Design Table** :
• Équipe déjà sur Ant Design
• Livraison rapide, tout inclus
• Back-office traditionnel
• Bundle plus lourd acceptable
**shadcn/ui + TanStack Table** :
• Stack Tailwind CSS
• Personnalisation poussée
• Flexibilité et performance
• Prêt à investir du code
**Données** :
Croissance 300 %+ (2024-2026) pour shadcn/ui + TanStack Table.
Les deux sont solides — choix selon stack et équipe.
Comment coordonner frontend et backend pour les permissions ?
**Frontend (middleware + composants)** :
• Objectif : UX, retour rapide
• Middleware + boutons conditionnels
• Contournable via DevTools — pas une barrière de sécurité
**Backend (API + Server Actions)** :
• Vraie défense
• Chaque action et route API
• Obligatoire pour opérations sensibles
**Config unifiée** :
• Module partagé
• Mêmes règles frontend/backend
• Évite les incohérences
**Leçon** : frontend soigné, une API oubliée — contournement Postman total.
Permissions dans le JWT ou requête BDD à chaque fois ?
**Option 1 : JWT (recommandé en général)** :
• Avantage : pas de requête BDD, performant
• Inconvénient : effet après expiration token
• Pour : permissions stables
• Expiration ~1 h
**Option 2 : cache Redis** :
• Temps réel immédiat
• Dépendance supplémentaire
• Pour : changements fréquents
**Option 3 : BDD directe** :
• 100 % temps réel
• Requête à chaque hit — lent
• Déconseillé sauf cas particulier
**Hybride** :
JWT + blacklist Redis des permissions révoquées.
Checklist sécurité avant mise en ligne ?
**Validation API** :
• Server Actions protégées
• Routes API vérifiées
• Double validation pour suppressions
**Anti-élévation de privilèges** :
• Pas de modification de son propre rôle
• Pas d'auto-attribution
• Ressources haut niveau protégées
**Audit et monitoring** :
• Logs critiques immuables
• Surveillance des 403
• Alertes super-admin
• Email changements de config
**Sessions** :
• Expiration ~1 h
• Déconnexion forcée
• Invalidation après changement mot de passe
**Entrées** :
• Validation frontend et backend
• Zod
• ORM contre injection SQL
Frontend = UX, backend = sécurité !
15 min de lecture · Publié le: 7 janv. 2026 · Mis à jour le: 27 juil. 2026
Guide complet Next.js
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
Guide complet d'upload de fichiers Next.js : upload direct S3/Qiniu via URL présignée
Apprenez à utiliser des URL présignées pour envoyer des fichiers directement vers S3/Qiniu dans Next.js, contourner la limite de 4 Mo, gérer des fichiers jusqu'à 5 Go, avec exemples complets, optimisation et bonnes pratiques de production.
Partie 38 sur 51
Suivant
Guide complet Next.js sur Vercel : variables d'environnement, domaines et monitoring
Guide complet pour déployer Next.js sur Vercel : variables d'environnement, domaine personnalisé, certificat SSL et monitoring — en évitant les pièges courants des débutants.
Partie 40 sur 51



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire