Next.js中App.getInitialProps内RTK SSR鉴权致页面导航报错
问题原因
你遇到的警告Cannot update a component ('HomeScreen') while rendering a different component ('App'),本质是因为在App.getInitialProps中调用RTK Query的initiate方法时,RTK Query会在请求完成后触发Redux状态更新,但此时App组件正处于渲染页面组件(比如HomeScreen)的过程中。页面组件已经通过useAppSelector读取了用户状态,状态的突然更新会导致页面组件在App渲染期间重新渲染,违反了React的渲染阶段不可更新状态的规则。
解决方案
方案1:改用RTK Query的useQuery Hook在App组件中获取用户
放弃在getInitialProps中手动dispatch请求,改用RTK Query提供的useQuery Hook,它会在组件挂载后执行请求,避免在渲染阶段触发状态更新。
修改_app.tsx的App组件:
function App({ Component, ...rest }: AppPropsWithLayout) { const { store, props: { pageProps }, } = wrapper.useWrappedStore(rest); const currentUser = selectCurrentUser(store.getState()); // 使用RTK Query的useQuery Hook获取用户,skip选项避免重复请求 const { data: user } = useMeQuery(undefined, { skip: !!currentUser, // 如果已有用户则跳过请求 onSuccess: (data) => { store.dispatch(setCredentials({ user: data })); }, }); const getLayout = Component.getLayout ?? ((page) => page); return ( <Provider store={store}>{getLayout(<Component {...pageProps} />)}</Provider> ); } // 移除getInitialProps中的dispatch逻辑 App.getInitialProps = wrapper.getInitialAppProps( (store) => async ({ ctx, Component }: AppContext) => { return { pageProps: { ...(Component.getInitialProps ? await Component.getInitialProps({ ...ctx, store }) : {}), }, }; }, );
优点:符合RTK Query的最佳实践,自动处理请求缓存、重复请求拦截和错误处理,无需手动管理请求生命周期。
方案2:直接调用API而非使用RTK Query的initiate
如果必须在getInitialProps中完成用户获取,可以绕过RTK Query的initiate方法,直接调用API并手动dispatch状态更新,避免RTK Query内部的状态更新时机问题。
修改App.getInitialProps:
App.getInitialProps = wrapper.getInitialAppProps( (store) => async ({ ctx, Component }: AppContext) => { try { // 服务器端需要传递Cookie,客户端自动携带 const headers: HeadersInit = {}; if (ctx.req) { headers.Cookie = ctx.req.headers.cookie || ''; } const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/me`, { method: 'GET', headers, credentials: 'include', }); if (res.ok) { const user = await res.json(); store.dispatch(setCredentials({ user })); } } catch (err) { // 按需处理错误,比如清除无效凭证 } return { pageProps: { ...(Component.getInitialProps ? await Component.getInitialProps({ ...ctx, store }) : {}), }, }; }, );
优点:完全控制请求流程,避免RTK Query带来的渲染阶段状态更新问题,适合需要严格控制初始化逻辑的场景。
方案3:将用户数据通过pageProps传递给页面
在getInitialProps中完成用户获取和状态更新后,将用户数据作为pageProps传递给页面组件,页面直接使用props中的数据而非从Redux读取,避免渲染阶段的状态依赖。
修改App.getInitialProps:
App.getInitialProps = wrapper.getInitialAppProps( (store) => async ({ ctx, Component }: AppContext) => { let user = null; try { // 使用unwrap()获取请求结果,确保请求完成后再更新状态 const { data } = await store.dispatch(me.initiate()).unwrap(); user = data; store.dispatch(setCredentials({ user })); } catch (err) { // 处理错误 } return { pageProps: { user, ...(Component.getInitialProps ? await Component.getInitialProps({ ...ctx, store, user }) : {}), }, }; }, );
页面组件修改为接收props:
export const HomeScreen = ({ user }) => { // 直接使用props中的user,无需useAppSelector ... }
优点:页面组件无需依赖Redux状态,初始化数据通过props传递,符合Next.js的服务端渲染数据传递规范。
内容的提问来源于stack exchange,提问作者monotype

