NextJS路由切换时页面及公共组件非预期重渲染问题咨询
问题1:路由跳转时旧页面组件重复执行的成因与解决方案
成因
你观察到的现象在Next.js Pages Router场景下是典型的上层组件重渲染连带未卸载的旧页面重渲染:
Next.js客户端路由切换的默认生命周期为:触发跳转→加载目标页面资源→渲染目标页面→目标页面渲染完成后卸载旧页面。在旧页面被正式卸载前,如果包裹所有页面的_app.js发生重渲染,所有还处于DOM树中的组件(包括待卸载的旧商品页)都会被连带触发重渲染,你写在组件顶层的console.log自然会被执行。
如果跳转时你修改了全局状态(Context/Redux等)且商品页依赖该状态,也会触发相同的重渲染行为。
解决方案
- 业务逻辑不要写在组件顶层作用域,仅应该在商品页挂载时执行的逻辑统一放到空依赖的
useEffect中,重渲染不会触发空依赖useEffect的执行:
useEffect(() => { // 加载第三方脚本、页面埋点等仅需执行一次的逻辑 }, [])
- 第三方脚本不要手动创建DOM插入,直接使用Next.js官方的
Script组件,给脚本添加唯一id属性,Next.js会自动去重,不会重复插入DOM:
import Script from 'next/script' // 商品页内引入专属脚本 <Script id="product-specific-script" src="xxx.js" strategy="afterInteractive" />
- 给页面组件包裹
React.memo,只要传入的props没有发生浅比较层面的变化,就不会触发重渲染:
export default React.memo(Product)
- 敏感逻辑可以加路由匹配判断,只有当前路由确实是商品页时才执行:
import { useRouter } from 'next/router' const Product = (props) => { const router = useRouter() // 非商品页路由直接返回空,不会执行后续逻辑 if(!router.pathname.startsWith('/product')) return null // 其余业务逻辑 }
问题2:公共组件频繁重渲染的成因与解决方案
成因
该现象不属于正常情况,绝大多数是_app.js的配置问题导致:
_app.js渲染时每次都生成新的引用类型props传给公共组件,比如直接写onClick={() => {}}、style={{}},每次渲染的props引用都发生变化,自然触发公共组件重渲染。_app.js本身订阅了路由变化或者全局状态,路由切换时状态更新导致_app.js整体重渲染,所有子组件都会跟着重渲染。- 公共组件订阅的全局状态在路由切换时发生了引用变化,哪怕你认为该状态和公共组件无关。
解决方案
- 给公共组件包裹
React.memo,需要传给公共组件的函数用useCallback包裹、引用类型数据用useMemo包裹,保证props引用稳定:
// _app.js中 const handleMenuClick = useCallback(() => { // 点击逻辑 }, [/* 依赖项 */]) const navStyle = useMemo(() => ({ color: '#333' }), []) // 传入公共组件 <Header onClick={handleMenuClick} style={navStyle} />
- 拆分全局Context,不要把所有状态都存在同一个Context里,避免单个状态变化触发所有订阅组件重渲染。
- 用React DevTools的Profiler面板,开启「Record why each component rendered」选项,可以直接定位到每个组件重渲染的具体触发原因。
内容的提问来源于stack exchange,提问作者Mike Willis
相关产品推荐
相关产品推荐

