Next.js仪表盘页面路由如何实现最小化重渲染?
问题
我当前的Dashboard组件结构如下:
export default function Dashboard({ children }) { return ( <> <DashboardLayout menu={menu}> <DashboardPage variant="cards" flow="one-one"> {children} </DashboardPage> </DashboardLayout> </> ) }
这个仪表盘左侧有侧边栏菜单用于切换不同DashboardPage,菜单、侧边栏、页脚等组件是各页面共享的,我希望避免这些组件重复渲染。但使用Next.js原生<Link>组件切换页面时,所有组件都会重渲染,目前我想到的替代方案是用useState或useReducer钩子控制页面渲染:
export default function Dashboard() { const [state, dispatch] = useReducer(); return ( <> <DashboardLayout menu={menu}> <DashboardPage variant="cards" flow="one-one"> {state == 'page1' && <DashboardPage1 />} {state == 'page2' && <DashboardPage2 />} {state == 'page3' && <DashboardPage3 />} ... {state == 'pageN' && <DashboardPageN />} </DashboardPage> </DashboardLayout> </> ) }
请问是否可以使用路由(如useRoute钩子)实现页面切换,同时避免DashboardLayout等无需重渲染的组件重复渲染?目前切换页面时,DashboardLayout内的控制台日志会打印4次。
解决方案
当然可以通过路由实现页面切换同时避免共享组件重渲染,核心思路是让DashboardLayout这类共享组件脱离路由切换的层级,只让页面内容部分响应路由变化,同时结合React的性能优化手段:
1. 利用Next.js嵌套路由结构(推荐)
不管你用的是App Router还是Pages Router,都可以通过嵌套路由让共享组件作为父路由的一部分,子路由仅渲染页面内容:
针对App Router(Next.js 13+)
- 新建路由结构:
app/ dashboard/ layout.jsx # 放置DashboardLayout page1/ page.jsx page2/ page.jsx page3/ page.jsx dashboard/layout.jsx内容:export default function DashboardLayout({ children }) { // 这里的内容仅首次加载时渲染,切换子路由不会重渲染 return ( <DashboardLayout menu={menu}> <DashboardPage variant="cards" flow="one-one"> {children} </DashboardPage> </DashboardLayout> ) }- 子路由页面(如
page1/page.jsx)直接导出页面组件:
切换export default function Page1() { return <DashboardPage1 /> }/dashboard/page1和/dashboard/page2时,仅children部分(子页面组件)会重新渲染,DashboardLayout不会重复渲染。
针对Pages Router
- 新建
pages/dashboard/[pageSlug].js作为动态路由,同时在pages/dashboard/_app.js里放置共享布局:pages/dashboard/_app.js:import DashboardLayout from '../../../components/DashboardLayout' import DashboardPage from '../../../components/DashboardPage' export default function DashboardApp({ Component, pageProps }) { return ( <DashboardLayout menu={menu}> <DashboardPage variant="cards" flow="one-one"> <Component {...pageProps} /> </DashboardPage> </DashboardLayout> ) }pages/dashboard/[pageSlug].js:export default function DashboardDynamicPage({ pageSlug }) { switch(pageSlug) { case 'page1': return <DashboardPage1 /> case 'page2': return <DashboardPage2 /> // 其他页面分支 default: return <NotFound /> } } export async function getStaticPaths() { return { paths: [{ params: { pageSlug: 'page1' } }, { params: { pageSlug: 'page2' } }], fallback: false } } export async function getStaticProps({ params }) { return { props: { pageSlug: params.pageSlug } } }
DashboardLayout作为_app.js里的组件,仅在进入dashboard路由组时渲染一次,切换子页面不会重渲染。
2. 优化组件本身的渲染逻辑
如果不想调整路由结构,也可以通过以下方式减少DashboardLayout的重渲染:
- 用
React.memo包裹DashboardLayout,确保仅当menu等props变化时才重渲染:
注意如果import { memo } from 'react' const DashboardLayout = memo(({ menu, children }) => { // 组件内容 })menu是引用类型(对象、数组),要用useMemo缓存保证引用稳定:const memoizedMenu = useMemo(() => menu, [/* 依赖项 */]) - 检查
DashboardLayout内部是否有不必要的状态更新或依赖项错误,比如useEffect依赖项数组不正确导致重复执行日志打印。
3. 结合路由状态控制渲染
如果你想继续用路由钩子(App Router的usePathname或Pages Router的useRouter)控制页面渲染,也能实现无重渲染效果:
import { usePathname } from 'next/navigation' // App Router // 或 import { useRouter } from 'next/router' // Pages Router export default function Dashboard() { const pathname = usePathname() // Pages Router写法:const { pathname } = useRouter() return ( <> <DashboardLayout menu={menu}> <DashboardPage variant="cards" flow="one-one"> {pathname.includes('page1') && <DashboardPage1 />} {pathname.includes('page2') && <DashboardPage2 />} {/* 其他页面分支 */} </DashboardPage> </DashboardLayout> </> ) }
配合React.memo包裹DashboardLayout,只要menu的引用稳定,DashboardLayout就不会因路由切换而重渲染。
内容的提问来源于stack exchange,提问作者Fabian Svensson
相关产品推荐
相关产品推荐

