Next.js的_app.js中使用SWR钩子是否会引发性能问题及如何优化
问题根因
你在_app.js层调用useUser并透传props给Layout的方案,会触发整站级别的重渲染:当useUser返回的用户状态更新时,整个App根组件及其所有子节点(包括进度条、当前页面组件等)都会重新渲染,渲染开销远大于仅Layout组件内部更新的场景,因此会出现明显的渲染滞后。
基于SWR的优化方案
1. 保持Layout内部调用useUser,复用SWR全局缓存
SWR默认对相同key的请求做全局缓存与状态同步,无论你在应用任何位置调用useUser,只要钩子内部使用的SWR请求key一致,就只会发起一次网络请求,且所有调用处的状态会同步更新。完全不需要将useUser提到_app层透传props,直接在Layout内部调用的方案本身就是最优的,既缩小了重渲染范围,也不会产生多余的网络请求。
如果嫌每个页面单独引入Layout繁琐,可以使用Next.js官方的单页Layout模式统一封装:
- 给每个页面组件添加静态
Layout属性,赋值为你的Layout组件 - 在
_app.js中统一调用该属性包装页面,不需要在_app层引入和传递任何用户相关参数:
// pages/_app.js 改造示例 function MyApp({ Component, pageProps }) { const getLayout = Component.getLayout || ((page) => <Layout>{page}</Layout>) return ( <UserProvider value={userState}> <UIProvider value={uiState}> <CloudinaryContext cloudName="hillfinders"> <Head> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" /> </Head> <NextNprogress color="#abf581" startPosition={0.3} stopDelayMs={200} height={4} showOnShallow /> {getLayout(<Component {...pageProps} />)} </CloudinaryContext> </UIProvider> </UserProvider> ) }
2. 登录后主动更新SWR缓存,消除状态更新延迟
用户登录成功后不要等待SWR自动重新验证获取用户信息,直接调用mutate方法主动更新SWR缓存,导航栏会立刻同步渲染登录后的状态,完全消除滞后:
// 登录接口调用成功后执行 mutate('/api/user', 新的用户数据, { revalidate: false })
其中第一个参数是你useUser内部使用的SWR请求key,第三个参数设为false表示不需要额外发起请求验证,直接用传入的新数据更新缓存。
3. 优化useUser钩子的SWR配置
给useUser内部的SWR请求添加如下配置,进一步提升响应速度:
// useUser 钩子内部改造示例 const useUser = () => { return useSWR('/api/user', fetcher, { dedupingInterval: 10 * 60 * 1000, // 10分钟内相同key的请求去重,避免重复发起请求 revalidateOnFocus: false, // 窗口聚焦时不需要重新验证用户状态,按需开启 keepPreviousData: true, // 加载新数据时保留之前的用户状态,避免界面跳变 }) }
4. 服务端预填充SWR缓存(可选)
如果你的应用做了服务端渲染,可以在getServerSideProps中预先请求用户信息,注入到SWR的fallback中,客户端加载时直接使用预取的用户数据,不需要等待客户端请求:
// 页面级getServerSideProps示例 export async function getServerSideProps(context) { const user = await fetchUser(context.req) // 服务端获取用户信息 return { props: { fallback: { '/api/user': user } } } }
然后在_app.js中将fallback传递给SWRProvider即可。
内容的提问来源于stack exchange,提问作者Antonio Pavicevac-Ortiz

