Next.js 15平行路由与拦截路由深度解析
1. Next.js 路由系统的演进与核心概念Next.js 的路由系统经历了从基础到复杂的演进过程。在早期版本中Next.js 采用了基于文件系统的简单路由机制随着框架的成熟逐渐引入了更高级的路由功能。在 Next.js 13 及之后的版本中App Router 的引入彻底改变了路由的组织方式为开发者提供了更强大的控制能力。平行路由Parallel Routes和拦截路由Intercepting Routes是 Next.js 15 中引入的两个重要特性。平行路由允许同时渲染多个页面内容而拦截路由则可以在页面加载前进行条件判断和重定向。这两个特性共同构成了 Next.js 路由系统的核心能力。提示Next.js 15 的路由系统向后兼容开发者可以逐步迁移现有项目到新路由系统不必一次性重写所有路由逻辑。1.1 路由系统的基本工作原理Next.js 的路由系统基于文件系统的约定。在app目录下文件夹结构直接映射为 URL 路径。例如app/ dashboard/ page.tsx # 对应 /dashboard settings/ page.tsx # 对应 /settings这种设计使得路由配置变得直观且易于维护。当用户访问某个 URL 时Next.js 会自动匹配对应的页面组件进行渲染。在底层Next.js 使用 React Server Components 和 Client Components 的组合来实现高效的页面渲染。1.2 平行路由与拦截路由的关系平行路由和拦截路由虽然都是路由系统的扩展功能但解决的问题不同平行路由解决同时展示多个独立内容区域的需求拦截路由解决在路由跳转前进行条件判断的需求这两种路由技术可以结合使用。例如在一个电商网站中使用拦截路由检查用户登录状态对已登录用户展示平行路由布局商品列表购物车侧边栏这种组合使用可以创建出既安全又功能丰富的用户界面。2. 平行路由的深度解析与实现平行路由是 Next.js 15 中引入的革命性功能它允许开发者在同一 URL 下同时渲染多个独立的内容区域。这种设计模式特别适合需要展示多个独立数据流的复杂应用场景。2.1 平行路由的核心机制平行路由的实现依赖于特殊的命名约定。在app目录下使用前缀的文件夹会被识别为平行路由插槽。例如app/ sidebar/ page.tsx main/ page.tsx layout.tsx在这个结构中sidebar和main是两个平行路由插槽。当访问根路径/时Next.js 会同时渲染这两个插槽的内容并将它们组合到布局中。平行路由的关键特性包括独立加载每个插槽的内容可以独立加载和更新独立错误处理一个插槽的错误不会影响其他插槽条件渲染可以根据需要动态显示/隐藏特定插槽2.2 平行路由的典型应用场景2.2.1 仪表盘布局在管理后台系统中平行路由可以完美实现复杂的仪表盘布局app/ dashboard/ sidebar/ page.tsx # 导航菜单 content/ page.tsx # 主内容区 notifications/ page.tsx # 通知面板 layout.tsx # 整体布局这种结构允许各个区域独立更新。例如当用户点击导航菜单时只需更新content区域而其他区域保持不变。2.2.2 多视图对比在数据分析应用中平行路由可以实现多数据视图的对比app/ analysis/ chart/ page.tsx # 图表视图 table/ page.tsx # 表格视图 stats/ page.tsx # 统计摘要 layout.tsx用户可以在同一页面同时查看不同形式的数据展示而无需来回切换标签页。2.3 平行路由的实现细节实现一个基本的平行路由需要以下几个步骤创建插槽文件夹使用前缀命名如sidebar在每个插槽中添加page.tsx文件创建布局文件layout.tsx使用props接收插槽内容export default function Layout({ children, sidebar, main }: { children: React.ReactNode sidebar: React.ReactNode main: React.ReactNode }) { return ( div classNamecontainer aside{sidebar}/aside main{main}/main {children} /div ) }在页面中使用插槽// app/page.tsx export default function Page() { return ( h1Parallel Routes Demo/h1 / ) }注意平行路由插槽的 props 名称必须与文件夹名称去掉前缀后一致。例如sidebar对应sidebarprop。3. 拦截路由的机制与实战应用拦截路由是 Next.js 15 中另一个强大的功能它允许你在用户访问某个页面之前进行拦截根据条件重定向到其他页面。这在实现权限控制和登录状态检查时非常有用。3.1 拦截路由的工作原理拦截路由通过在路由跳转前插入中间件逻辑来实现其功能。当用户尝试访问某个路由时Next.js 会先执行拦截逻辑然后决定是继续原始请求还是重定向到其他页面。拦截路由的关键特性包括透明重定向用户感知不到被拦截的过程条件判断可以基于任意条件进行拦截上下文保留拦截时可以保留原始请求的上下文信息3.2 拦截路由的常见使用场景3.2.1 身份验证最常见的应用是登录状态检查。当未登录用户尝试访问受保护页面时自动重定向到登录页app/ profile/ page.tsx # 用户资料页 login/ page.tsx # 登录页 middleware.ts # 拦截逻辑middleware.ts内容示例import { NextResponse } from next/server import type { NextRequest } from next/server export function middleware(request: NextRequest) { const token request.cookies.get(authToken)?.value if (!token request.nextUrl.pathname.startsWith(/profile)) { return NextResponse.redirect(new URL(/login, request.url)) } return NextResponse.next() }3.2.2 功能引导对于新用户可以拦截关键操作页面引导他们完成初始设置export function middleware(request: NextRequest) { const isNewUser request.cookies.get(isNewUser)?.value true if (isNewUser request.nextUrl.pathname.startsWith(/dashboard)) { return NextResponse.redirect(new URL(/welcome, request.url)) } return NextResponse.next() }3.2.3 A/B 测试拦截路由可以用于实现无侵入的 A/B 测试export function middleware(request: NextRequest) { const variant Math.random() 0.5 ? a : b if (request.nextUrl.pathname /landing) { return NextResponse.rewrite(new URL(/landing/${variant}, request.url)) } return NextResponse.next() }3.3 拦截路由的高级用法3.3.1 动态路径拦截拦截路由不仅限于固定路径还可以处理动态路由export function middleware(request: NextRequest) { const pathname request.nextUrl.pathname if (pathname.match(/^\/posts\/\d\/edit$/)) { // 检查用户是否有编辑权限 if (!checkEditPermission(request)) { return NextResponse.redirect(new URL(/unauthorized, request.url)) } } return NextResponse.next() }3.3.2 条件重写除了重定向拦截路由还可以重写 URL让用户保持原始 URL 的同时显示不同内容export function middleware(request: NextRequest) { if (request.nextUrl.pathname /legacy) { return NextResponse.rewrite(new URL(/modern, request.url)) } return NextResponse.next() }3.3.3 多条件组合可以组合多个条件进行复杂拦截逻辑export function middleware(request: NextRequest) { const { pathname } request.nextUrl const isMobile request.headers.get(user-agent)?.includes(Mobile) if (isMobile pathname.startsWith(/desktop-only)) { return NextResponse.redirect(new URL(/mobile-alternative, request.url)) } if (pathname /special-offer) { const hasSeenOffer request.cookies.get(seenOffer)?.value true if (!hasSeenOffer) { const response NextResponse.next() response.cookies.set(seenOffer, true) return response } } return NextResponse.next() }4. 平行路由与拦截路由的综合应用将平行路由和拦截路由结合使用可以构建出既灵活又安全的现代 Web 应用。这种组合特别适合需要复杂布局和严格权限控制的企业级应用。4.1 典型组合应用架构一个结合两种路由技术的典型项目结构如下app/ auth/ login/ page.tsx register/ page.tsx main/ dashboard/ page.tsx settings/ page.tsx layout.tsx middleware.ts在这种架构中使用拦截路由处理身份验证使用平行路由组织认证和非认证区域布局文件负责整体的视觉结构4.2 实现步骤详解4.2.1 配置拦截中间件首先设置middleware.ts处理认证逻辑import { NextResponse } from next/server import type { NextRequest } from next/server const publicPaths [/login, /register] const authPaths [/dashboard, /settings] export function middleware(request: NextRequest) { const token request.cookies.get(authToken)?.value const pathname request.nextUrl.pathname if (!token authPaths.some(path pathname.startsWith(path))) { return NextResponse.redirect(new URL(/login, request.url)) } if (token publicPaths.some(path pathname.startsWith(path))) { return NextResponse.redirect(new URL(/dashboard, request.url)) } return NextResponse.next() }4.2.2 设计平行路由布局在layout.tsx中定义整体结构export default function Layout({ auth, main, }: { auth: React.ReactNode main: React.ReactNode }) { const isAuthenticated checkAuth() // 从cookie或context获取 return ( div classNameapp-container {isAuthenticated ? ( Navigation / main classNamecontent{main}/main / ) : ( div classNameauth-container{auth}/div )} /div ) }4.2.3 实现动态内容切换利用平行路由的特性可以根据状态动态切换显示内容export default function Layout({ auth, main, }: { auth: React.ReactNode main: React.ReactNode }) { const [isLoading, setIsLoading] useState(true) const [isAuthenticated, setIsAuthenticated] useState(false) useEffect(() { async function checkAuth() { const res await fetch(/api/check-auth) setIsAuthenticated(res.ok) setIsLoading(false) } checkAuth() }, []) if (isLoading) { return LoadingSpinner / } return ( {isAuthenticated ? main : auth} / ) }4.3 性能优化技巧4.3.1 按需加载平行路由对于不常用的平行路由插槽可以使用动态导入减少初始加载体积const Sidebar dynamic(() import(/app/sidebar/page)) export default function Layout({ sidebar }: { sidebar: React.ReactNode }) { const [showSidebar, setShowSidebar] useState(false) return ( div button onClick{() setShowSidebar(!showSidebar)} Toggle Sidebar /button {showSidebar Sidebar /} /div ) }4.3.2 拦截路由的路径匹配优化在middleware.ts中精确控制拦截路径可以提高性能export const config { matcher: [ /dashboard/:path*, /settings/:path*, /login, /register ] }这样中间件只会处理指定的路径减少不必要的执行。4.3.3 平行路由的独立加载状态为每个平行路由插槽添加独立的加载状态提升用户体验export default function Layout({ sidebar, main, }: { sidebar: React.ReactNode main: React.ReactNode }) { return ( div Suspense fallback{SidebarLoader /} {sidebar} /Suspense Suspense fallback{MainContentLoader /} {main} /Suspense /div ) }5. 常见问题与解决方案在实际项目中使用平行路由和拦截路由时开发者可能会遇到各种问题。以下是经过实战验证的解决方案。5.1 平行路由的常见问题5.1.1 插槽内容不显示问题现象配置了平行路由插槽但内容没有渲染出来。排查步骤检查文件夹命名是否正确必须使用前缀确认布局组件是否正确接收了插槽 props验证插槽文件夹内是否有page.tsx文件检查父级布局是否意外覆盖了插槽内容解决方案// 确保布局组件正确解构props export default function Layout({ sidebar, // 必须与插槽名称匹配 children }: { sidebar: React.ReactNode children: React.ReactNode }) { return ( div aside{sidebar}/aside main{children}/main /div ) }5.1.2 插槽间状态共享困难问题现象多个平行路由插槽需要共享状态但直接传递props很麻烦。解决方案使用 React Context 提供共享状态或者使用状态管理库如 Zustand、Jotai对于服务器组件可以通过布局组件传递数据示例// 在布局中创建上下文 export default function Layout({ sidebar, main }: { sidebar: React.ReactNode, main: React.ReactNode }) { const [sharedState, setSharedState] useState(null) return ( SharedContext.Provider value{{ sharedState, setSharedState }} {sidebar} {main} /SharedContext.Provider ) }5.2 拦截路由的常见问题5.2.1 中间件无限重定向问题现象拦截逻辑导致页面不断重定向形成循环。原因分析通常是因为重定向目标也被中间件拦截。解决方案明确排除不需要拦截的路径添加条件判断避免循环修正后的中间件示例export const config { matcher: [/((?!login|register|_next|favicon.ico).*)] } export function middleware(request: NextRequest) { const token request.cookies.get(authToken)?.value const pathname request.nextUrl.pathname if (!token !pathname.startsWith(/auth)) { return NextResponse.redirect(new URL(/auth/login, request.url)) } return NextResponse.next() }5.2.2 拦截路由与静态生成冲突问题现象使用getStaticProps的页面在拦截后显示异常。解决方案对于需要拦截的静态页面改用客户端渲染或者在中间件中添加对预渲染请求的特殊处理中间件调整export function middleware(request: NextRequest) { // 跳过预渲染请求 if (request.headers.get(x-nextjs-data)) { return NextResponse.next() } // 正常拦截逻辑 if (shouldIntercept(request)) { return NextResponse.redirect(new URL(/login, request.url)) } return NextResponse.next() }5.3 性能相关问题5.3.1 平行路由导致包体积过大问题现象使用多个平行路由后JavaScript 包体积显著增加。优化方案对非关键插槽使用动态导入配置适当的代码分割策略使用 React 的lazy和Suspense优化示例const DynamicSidebar dynamic(() import(/app/sidebar/page), { ssr: false, loading: () SidebarSkeleton / }) export default function Layout() { return ( div Suspense fallback{divLoading.../div} DynamicSidebar / /Suspense /div ) }5.3.2 拦截中间件执行时间过长问题现象中间件中的复杂逻辑导致页面加载延迟。优化方案将耗时操作移到 API 路由使用边缘函数减少延迟缓存中间件决策结果优化后的中间件export const config { runtime: edge } export default async function middleware(request: NextRequest) { // 快速检查缓存 const cachedDecision await getFromCache(request) if (cachedDecision) { return cachedDecision } // 简单快速的检查 if (request.nextUrl.pathname /) { return NextResponse.next() } // 复杂检查移到API路由 const checkResponse await fetch( ${process.env.API_URL}/route-check, { headers: request.headers } ) const decision await checkResponse.json() await cacheDecision(request, decision) return decision.allow ? NextResponse.next() : NextResponse.redirect(new URL(decision.redirectTo, request.url)) }6. 高级技巧与最佳实践在掌握了平行路由和拦截路由的基础用法后下面这些高级技巧可以帮助你构建更健壮、更高效的 Next.js 应用。6.1 动态平行路由插槽传统的平行路由需要在构建时确定插槽结构但通过一些技巧可以实现动态插槽。6.1.1 基于条件的动态插槽渲染export default function Layout({ sidebar, analytics, main }: { sidebar: React.ReactNode analytics?: React.ReactNode main: React.ReactNode }) { const showAnalytics useFeatureFlag(analytics) return ( div {sidebar} {main} {showAnalytics analytics} /div ) }对应的文件夹结构app/ sidebar/ page.tsx analytics/ page.tsx main/ page.tsx6.1.2 完全动态的插槽加载async function DynamicSlot({ slotName }: { slotName: string }) { const Component await import(/app/${slotName}/page) return Component.default / } export default function Layout({ slot }: { slot: string }) { return ( Suspense fallback{divLoading slot.../div} DynamicSlot slotName{slot} / /Suspense ) }6.2 拦截路由的进阶模式6.2.1 基于角色的动态拦截export function middleware(request: NextRequest) { const session getSession(request) const pathname request.nextUrl.pathname // 管理员专属路径 if (pathname.startsWith(/admin) session.role ! admin) { return NextResponse.redirect(new URL(/unauthorized, request.url)) } // 付费用户专属路径 if (pathname.startsWith(/premium) !session.isPremium) { return NextResponse.redirect(new URL(/upgrade, request.url)) } return NextResponse.next() }6.2.2 A/B 测试与功能灰度export function middleware(request: NextRequest) { const userId getUserId(request) const pathname request.nextUrl.pathname // 新首页实验组 if (pathname / isInExperimentGroup(userId, new-homepage)) { return NextResponse.rewrite(new URL(/home-v2, request.url)) } // 灰度发布新功能 if (pathname.startsWith(/new-feature) !isFeatureEnabled(userId, new-feature)) { return NextResponse.redirect(new URL(/coming-soon, request.url)) } return NextResponse.next() }6.3 性能监控与优化6.3.1 平行路由性能追踪export default function Layout({ sidebar, main }: { sidebar: React.ReactNode, main: React.ReactNode }) { useEffect(() { const measureSlotRender (slotName: string) { const start performance.now() return () { const duration performance.now() - start trackMetric(slot_${slotName}_render, duration) } } const cleanupSidebar measureSlotRender(sidebar) const cleanupMain measureSlotRender(main) return () { cleanupSidebar() cleanupMain() } }, []) return ( div div onLoad{cleanupSidebar}{sidebar}/div div onLoad{cleanupMain}{main}/div /div ) }6.3.2 拦截路由性能日志export function middleware(request: NextRequest) { const start performance.now() // 执行拦截逻辑 const response handleInterception(request) const duration performance.now() - start logMiddlewareDuration(request.nextUrl.pathname, duration) return response }6.4 测试策略6.4.1 平行路由的单元测试import { render } from testing-library/react import Layout from ./layout describe(Parallel Routes Layout, () { it(should render all slots, () { const { getByText } render( Layout sidebar{divSidebar Content/div} main{divMain Content/div} / ) expect(getByText(Sidebar Content)).toBeInTheDocument() expect(getByText(Main Content)).toBeInTheDocument() }) })6.4.2 拦截路由的集成测试import { createMiddlewareRunner } from next-test-utils import middleware from ./middleware describe(Intercepting Middleware, () { const runMiddleware createMiddlewareRunner(middleware) it(should redirect unauthorized users, async () { const response await runMiddleware({ url: /dashboard, cookies: {} }) expect(response.headers.get(location)).toBe(/login) }) it(should allow authorized users, async () { const response await runMiddleware({ url: /dashboard, cookies: { authToken: valid-token } }) expect(response.headers.get(location)).toBeNull() }) })7. 实战案例构建一个安全的内容管理系统让我们通过一个完整的案例展示如何结合平行路由和拦截路由构建一个安全的内容管理系统CMS。7.1 项目结构与规划app/ admin/ posts/ page.tsx users/ page.tsx auth/ login/ page.tsx register/ page.tsx public/ blog/ [slug]/ page.tsx home/ page.tsx layout.tsx middleware.ts7.2 核心实现代码7.2.1 中间件实现 (middleware.ts)import { NextResponse } from next/server import type { NextRequest } from next/server const PUBLIC_PATHS [/home, /blog] const ADMIN_PATHS [/admin] export function middleware(request: NextRequest) { const token request.cookies.get(authToken)?.value const role request.cookies.get(userRole)?.value const { pathname } request.nextUrl // 公共路径直接放行 if (PUBLIC_PATHS.some(path pathname.startsWith(path))) { return NextResponse.next() } // 管理员路径检查 if (ADMIN_PATHS.some(path pathname.startsWith(path))) { if (role ! admin) { return NextResponse.redirect(new URL(/unauthorized, request.url)) } return NextResponse.next() } // 其他认证路径检查 if (!token) { return NextResponse.redirect(new URL(/auth/login, request.url)) } return NextResponse.next() }7.2.2 主布局实现 (layout.tsx)export default function Layout({ admin, auth, public: publicContent, }: { admin?: React.ReactNode auth?: React.ReactNode public?: React.ReactNode }) { const { isAuthenticated, role } useAuth() if (!isAuthenticated) { return AuthLayout{auth}/AuthLayout } if (role admin) { return ( AdminLayout AdminSidebar / main{admin}/main /AdminLayout ) } return PublicLayout{publicContent}/PublicLayout }7.3 关键功能实现7.3.1 动态权限检查在 API 路由中添加权限验证// app/api/admin/route.ts export async function GET(request: Request) { const session await getSession(request) if (session?.role ! admin) { return new Response(Unauthorized, { status: 403 }) } // 处理管理员请求 const data await getAdminData() return Response.json(data) }7.3.2 实时通知系统利用平行路由实现实时通知面板// app/notifications/page.tsx export default function Notifications() { const [notifications, setNotifications] useState([]) useEffect(() { const socket new WebSocket(process.env.NOTIFICATION_WS_URL) socket.onmessage (event) { setNotifications(prev [...prev, JSON.parse(event.data)]) } return () socket.close() }, []) return ( div classNamenotification-panel {notifications.map(notification ( NotificationItem key{notification.id} data{notification} / ))} /div ) }7.4 部署注意事项7.4.1 中间件部署配置在next.config.js中确保中间件正确配置module.exports { experimental: { middleware: true, }, }7.4.2 边缘函数兼容性如果使用边缘函数注意某些 Node.js API 不可用export const config { runtime: edge, regions: [iad1] // 指定部署区域 } export default function middleware(request: NextRequest) { // 注意在边缘运行时中不能使用Node.js特定API return NextResponse.next() }7.4.3 静态资源处理确保中间件不拦截静态资源请求export const config { matcher: [/((?!_next|images|favicon.ico).*)] }8. 未来演进与技术前瞻Next.js 路由系统仍在快速发展中了解其未来方向有助于我们做出更好的架构决策。8.1 Next.js 路由系统的演进趋势根据官方路线图和社区讨论我们可以预见以下发展方向更细粒度的路由控制可能会引入路由组件的生命周期钩子增强的并行加载能力进一步优化多个平行路由的加载性能服务端组件深度集成平行路由与服务端组件的更紧密配合智能预加载策略基于用户行为的自动路由预加载8.2 即将到来的新特性虽然这些特性尚未正式发布但已经出现在官方讨论中条件平行路由基于运行时条件动态决定渲染哪些插槽ConditionalSlot when{shouldShowSlot} slot{SlotComponent /} /拦截路由的增强API更丰富的拦截条件和操作export function middleware(request) { if (request.isBot) { return Response.rewrite(new URL(/bot-page, request.url)) } }路由级性能分析内置的路由加载时间监控RouteProfile ProtectedRoute / /RouteProfile8.3 社区最佳实践的演进方向随着更多项目采用这些高级路由特性社区正在形成一些新的最佳实践微前端架构使用平行路由实现微前端集成渐进式路由迁移逐步将传统页面迁移到新路由系统类型安全路由通过 TypeScript 增强路由安全性自动化路由测试开发路由专用的测试工具链8.4 技术选型建议在选择是否使用这些高级路由特性时考虑以下因素项目规模小型项目可能不需要复杂的路由系统团队熟悉度确保团队有能力维护这些高级特性长期维护性评估路由设计的长期可维护性性能影响测量路由复杂性对性能的实际影响对于大多数项目建议新项目直接使用新路由系统现有项目逐步迁移关键路由复杂管理后台优先采用平行路由需要严格权限控制的系统使用拦截路由9. 迁移策略与升级路径对于已有 Next.js 项目如何平稳地引入平行路由和拦截路由功能以下是经过实战验证的迁移方案。9.1 从 Pages Router 到 App Router 的迁移9.1.1 渐进式迁移策略并行运行阶段保持现有pages目录不变新建app目录逐步迁移路由使用middleware.ts处理路由重定向迁移顺序建议graph LR A[公共页面] -- B[认证流程] B -- C[核心功能] C -- D[管理后台]配置兼容性设置// next.config.js module.exports { experimental: { appDir: true, }, }9.1.2 路由映射处理在middleware.ts中处理新旧路由映射export function middleware(request: NextRequest) { const pathname request.nextUrl.pathname // 旧路由重定向 const oldRoutes { /old-dashboard: /dashboard, /legacy-profile: /profile } if (oldRoutes[pathname]) { return NextResponse.redirect(new URL(oldRoutes[pathname], request.url)) } return NextResponse.next() }9.2 引入平行路由的步骤9.2.1 识别可并行化的界面区域适合转换为平行路由的典型区域导航菜单侧边栏通知面板模态对话框多视图面板9.2.2 分步转换示例原始结构app/ dashboard/ page.tsx # 包含菜单和主内容转换后结构app/ dashboard/ sidebar/ page.tsx main/ page.tsx layout.tsx迁移过程提取现有页面中的菜单部分到sidebar/page.tsx将主内容移到main/page.tsx创建布局文件组合这些插槽9.3 添加拦截路由的注意事项9.3.1 从基础拦截开始初始实现简单的身份检查// middleware.ts export function middleware(request: NextRequest) { if (request.nextUrl.pathname.startsWith(/protected) !isAuthenticated(request)) { return NextResponse.redirect(new URL(/login, request.url)) } return NextResponse.next() }9.3.2 逐步增加复杂逻辑后续添加更多拦截条件export function middleware(request: NextRequest) { // 身份验证 if (needsAuth(request) !isAuthenticated(request)) { return redirectToLogin(request) } // 权限检查 if (needsRole(request) !hasRequiredRole(request)) { return redirectToUnauthorized(request) } // 功能开关 if (isFeatureDisabled(request)) { return redirectToFeatureDisabled(request) } return NextResponse.next() }9.4 测试迁移的完整性9.4.1 自动化测试策略路由跳转测试describe(Route Redirects, () { it(should redirect /old to /new, async () { const response await fetch(/old) expect(response.redirected).toBe(true) expect(response.url).toContain(/new) }) })平行路由渲染测试test(renders all parallel slots, () { render( Layout sidebar{divSidebar/div} main{divMain/div} / ) expect(screen.getByText(Sidebar)).toBeInTheDocument() expect(screen.getByText(Main)).toBeInTheDocument() })拦截功能测试describe(Middleware Interception, () { it(should block unauthorized access, async () { const response await fetch(/admin, { headers: { Cookie: } }) expect(response.status).toBe(302) }) })9.4.2 监控生产环境部署后监控关键指标路由