Next.js 14中如何将中间件获取的用户权限与menu.tsx菜单项集成?
在Next.js 14中实现权限驱动的动态菜单
核心思路
因为权限存储在HTTP-only Cookie的accessToken中,只能在服务器端读取,所以要通过服务器组件或中间件传递权限到服务器端,再基于权限过滤静态菜单数组,实现动态显示/隐藏。
方案一:服务器组件直接读取Cookie解析权限(推荐)
利用Next.js 14 App Router的服务器组件特性,直接读取Cookie解析权限,过滤菜单后渲染。
步骤1:扩展菜单类型,添加权限字段
修改menu.tsx,给需要权限控制的菜单项添加requiredPermission字段:
// menu.tsx import { Gauge, Users } from 'lucide-react'; export interface MenuProps { href: string; icon: React.ReactNode; title: string; requiredPermission?: string; // 新增:该菜单项所需权限 } export const Menu: MenuProps[] = [ { href: '/dashboard', icon: <Gauge size={20} />, title: 'Dashboard', requiredPermission: 'Dashboard.Read' }, { href: '/users', icon: <Users size={20} />, title: '用户管理', requiredPermission: 'User.Read' }, // 其他无需权限的菜单项可以省略该字段 ];
步骤2:创建服务器组件处理权限与菜单过滤
创建ServerSideMenu.tsx服务器组件,读取Cookie解析权限并过滤菜单:
// app/components/ServerSideMenu.tsx import { cookies } from 'next/headers'; import { Menu as RawMenu } from '../menu'; // 解析accessToken获取权限的工具函数 function parsePermissionsFromToken(token: string): string[] { try { // 示例:JWT解码(根据你的token格式调整) const payload = JSON.parse(atob(token.split('.')[1])); return payload.permissions || []; } catch (err) { console.error('解析权限失败:', err); return []; } } export default function ServerSideMenu() { // 从Cookie读取accessToken const accessToken = cookies().get('accessToken')?.value; const userPermissions = accessToken ? parsePermissionsFromToken(accessToken) : []; // 根据权限过滤菜单 const filteredMenu = RawMenu.filter(item => { // 无权限要求的菜单项直接显示 if (!item.requiredPermission) return true; // 检查用户是否拥有所需权限 return userPermissions.includes(item.requiredPermission); }); // 渲染菜单UI return ( <nav className="sidebar"> <ul> {filteredMenu.map(item => ( <li key={item.href} className="menu-item"> <a href={item.href}> {item.icon} <span>{item.title}</span> </a> </li> ))} </ul> </nav> ); }
步骤3:在布局/页面中使用服务器组件
在根布局或需要显示菜单的页面中引入该组件:
// app/layout.tsx import ServerSideMenu from './components/ServerSideMenu'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="zh-CN"> <body> <ServerSideMenu /> <main className="content">{children}</main> </body> </html> ); }
方案二:中间件传递权限到服务器组件
如果已在中间件中解析权限,可以通过自定义响应头将权限传递给服务器组件,再过滤菜单。
步骤1:修改中间件添加权限头
// middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { const accessToken = request.cookies.get('accessToken')?.value; let permissions: string[] = []; if (accessToken) { // 复用你的权限解析逻辑 try { const payload = JSON.parse(atob(accessToken.split('.')[1])); permissions = payload.permissions || []; } catch (err) {} } // 将权限转为JSON字符串,通过自定义头传递 const response = NextResponse.next(); response.headers.set('x-user-permissions', JSON.stringify(permissions)); return response; } export const config = { matcher: ['/', '/dashboard/:path*', '/users/:path*'], // 匹配需要权限校验的路径 };
步骤2:服务器组件读取权限头过滤菜单
// app/components/ServerSideMenu.tsx import { headers } from 'next/headers'; import { Menu as RawMenu } from '../menu'; export default function ServerSideMenu() { // 读取中间件添加的自定义头 const permissionsHeader = headers().get('x-user-permissions'); const userPermissions = permissionsHeader ? JSON.parse(permissionsHeader) : []; // 过滤菜单逻辑同方案一 const filteredMenu = RawMenu.filter(item => { if (!item.requiredPermission) return true; return userPermissions.includes(item.requiredPermission); }); // 渲染菜单UI... }
方案三:服务器组件传递权限给客户端组件
如果菜单需要客户端交互(比如折叠、高亮),可以在服务器组件中解析权限,再传递给客户端组件使用:
步骤1:创建客户端菜单组件
// app/components/ClientMenu.tsx 'use client'; import { MenuProps } from '../menu'; interface ClientMenuProps { permissions: string[]; menuItems: MenuProps[]; } export default function ClientMenu({ permissions, menuItems }: ClientMenuProps) { const filteredMenu = menuItems.filter(item => { if (!item.requiredPermission) return true; return permissions.includes(item.requiredPermission); }); // 这里可以添加客户端交互逻辑,比如点击折叠等 return ( <nav className="sidebar"> <ul> {filteredMenu.map(item => ( <li key={item.href} className="menu-item"> <a href={item.href}> {item.icon} <span>{item.title}</span> </a> </li> ))} </ul> </nav> ); }
步骤2:服务器组件封装权限传递
// app/components/MenuWrapper.tsx import { cookies } from 'next/headers'; import { Menu as RawMenu } from '../menu'; import ClientMenu from './ClientMenu'; import { parsePermissionsFromToken } from './utils'; // 复用解析函数 export default function MenuWrapper() { const accessToken = cookies().get('accessToken')?.value; const permissions = accessToken ? parsePermissionsFromToken(accessToken) : []; return <ClientMenu permissions={permissions} menuItems={RawMenu} />; }
注意事项
- HTTP-only Cookie安全:所有权限解析逻辑都在服务器端完成,避免客户端接触accessToken,符合安全要求。
- 权限解析容错:添加异常捕获,防止token无效导致菜单加载失败。
- 菜单项默认显示:未添加
requiredPermission的菜单项默认显示,无需额外配置。
内容的提问来源于stack exchange,提问作者hantoren
相关产品推荐
相关产品推荐

