You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js中App.getInitialProps内RTK SSR鉴权致页面导航报错

解决Next.js中App.getInitialProps触发状态更新导致的组件更新警告

问题原因

你遇到的警告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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 11:10:50