Next.js搭配Framer Motion实现页面转场按返回键报错如何解决?
错误产生原因
- 路由事件监听未做副作用管理:直接在MyApp函数体中注册Router事件监听,没有包裹在useEffect钩子中,也没有对应的清理逻辑,会导致每次App组件重渲染都重复注册事件,浏览器回退触发路由变化时会多次执行NProgress的回调,和Framer Motion的动画执行时序产生冲突,触发渲染异常。
- 页面组件的唯一键取值不合理:用
router.route作为Component的key,该值是路由模板字符串(动态路由下所有参数不同的同模板路由key都相同),无法保证所有路由场景下的key唯一性,会导致AnimatePresence无法正确识别页面切换,触发组件卸载动画执行时的状态异常。 - 动画节点的层级冲突:在App层用AnimatePresence包裹页面组件,同时页面内部Layout的motion.div又声明了exit动画属性,两层的动画时序没有对齐,回退场景下容易出现动画执行时组件已经被卸载的情况,触发React错误边界的回退。
可行修复方案
- 修复路由事件的副作用管理:把Router事件的注册逻辑移到useEffect中,同时返回清理函数卸载事件监听器,修改后的_app.js相关代码如下:
import { useEffect } from 'react' function MyApp({ Component, pageProps, router }) { useEffect(() => { NProgress.configure({ minimum: 0.3, easing: 'ease', speed: 800, showSpinner: true, }); const handleStart = () => NProgress.start(); const handleEnd = () => NProgress.done(); Router.events.on('routeChangeStart', handleStart); Router.events.on('routeChangeComplete', handleEnd); Router.events.on('routeChangeError', handleEnd); // 组件卸载时清理事件监听 return () => { Router.events.off('routeChangeStart', handleStart); Router.events.off('routeChangeComplete', handleEnd); Router.events.off('routeChangeError', handleEnd); } }, []) return ( <> <AnimatePresence exitBeforeEnter> <Component {...pageProps} key={router.asPath} /> </AnimatePresence> <ToastContainer progressClassName="toastProgress" bodyClassName="toastBody" position="top-right" autoClose={5000} hideProgressBar={false} newestOnTop={false} draggable={false} pauseOnVisibilityChange closeOnClick pauseOnHover /> </> ); }
- 修改页面组件的key取值:把原有的
router.route替换为router.asPath,这个值是完整的实际路由路径(包含动态参数),可以保证不同路由页面的key唯一,正确触发AnimatePresence的动画流程。 - 统一动画层级:要么把motion动画的声明全部放到App层包裹Component的节点上,要么把AnimatePresence移到Layout组件内部,避免两层动画逻辑冲突。如果选择保留Layout内的动画逻辑,可以去掉App层的AnimatePresence包裹,统一在Layout内管理进入退出动画即可。
- 可选优化:如果调整后仍有偶发报错,可以给AnimatePresence加上
mode="wait"属性,确保前一个页面完全卸载、动画执行完成后再渲染新页面,避免状态冲突。
内容的提问来源于stack exchange,提问作者Nick
相关产品推荐
相关产品推荐

