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

使用Apollo Client useQuery在Next.js中出现Hydration错误的问题

解决Next.js Pages路由与Apollo Client的Hydration不一致错误

问题背景

在Next.js 12.1.6(Pages路由)搭配Apollo Client 3.3.19时,使用useQuery会触发Hydration错误:SSR阶段loading状态为false(服务端已获取数据并渲染目标组件),但客户端初始渲染时loading为true,导致服务端与客户端生成的HTML结构不匹配,触发Hydration冲突。目前用useEffect+useLazyQuery能临时解决,但需要更优方案。

问题代码示例:

// 服务端渲染组件,客户端初始渲染却显示"Loading",触发Hydration错误
function Page () {
    const {data, loading} = useQuery(query)
    
    if(loading) {
        return "Loading"
    }

    return <Component data={data} />
}

依赖版本:

@apollo/client : 3.3.19
next: 12.1.6 (pages router)

核心原因

Apollo Client在SSR时会预取数据并存入缓存,但客户端初始化阶段,服务端传递的缓存数据还未完成同步(即客户端缓存未完成Hydration),此时useQuery会判定数据未加载,loading状态为true,和服务端的渲染结果产生冲突。

最优解决方案

方法1:配置Apollo Client的ssrForceFetchDelay

在初始化Apollo Client时添加ssrForceFetchDelay选项,让客户端在初始渲染时短暂等待,确保服务端缓存同步完成,避免loading状态不一致。

示例代码:

// apollo-client.js
import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: '/api/graphql',
  cache: new InMemoryCache(),
  ssrMode: typeof window === 'undefined', // 服务端启用SSR模式
  ssrForceFetchDelay: 100, // 延迟客户端初始查询,等待缓存同步
});

export default client;

这个配置会让客户端在初始渲染时延迟指定时间再执行查询,确保服务端传递的缓存数据已同步到客户端,此时loading状态会和服务端保持一致。

方法2:通过getServerSideProps预取数据并传递缓存

在页面的getServerSideProps中手动用Apollo Client查询数据,将缓存状态传递给页面组件,组件直接复用缓存数据,避免在组件内调用useQuery导致的状态差异。

示例代码:

// pages/[page].js
import { initializeApollo } from '../apollo-client';
import { query } from '../queries';

export async function getServerSideProps(context) {
  const apolloClient = initializeApollo();
  
  await apolloClient.query({ query });
  
  return {
    props: {
      initialApolloState: apolloClient.cache.extract(), // 传递服务端缓存状态给客户端
    },
  };
}

function Page({ initialApolloState }) {
  // 初始化客户端时注入服务端缓存
  const client = initializeApollo(initialApolloState);
  const { data } = client.readQuery({ query }); // 直接从缓存读取数据,无loading状态

  return <Component data={data} />;
}

export default Page;

原理是通过getServerSideProps在服务端完成数据查询,将缓存状态传递给客户端,客户端初始化时直接复用缓存数据,组件内无需等待loading,服务端和客户端渲染结果完全一致。

方法3:用useEffect标记客户端Hydration完成

如果不想修改全局配置或服务端逻辑,可以在组件内用useEffect标记客户端是否完成初始Hydration,仅在客户端完成Hydration后才显示loading,避免服务端和客户端的渲染差异。

示例代码:

import { useState, useEffect } from 'react';
import { useQuery } from '@apollo/client';

function Page() {
  const [isClientHydrated, setIsClientHydrated] = useState(false);
  const { data, loading } = useQuery(query);

  useEffect(() => {
    // useEffect仅在客户端执行,标记Hydration完成
    setIsClientHydrated(true);
  }, []);

  // 服务端渲染时跳过loading分支,客户端Hydrated后再处理loading状态
  if (isClientHydrated && loading) {
    return "Loading";
  }

  return <Component data={data} />;
}

这个方案利用useEffect只在客户端执行的特性,确保服务端渲染时不会进入loading分支,客户端完成Hydration后再处理loading状态,避免HTML结构不一致。

临时方案对比

之前用useEffect+useLazyQuery的方式需要手动触发查询,代码冗余且可能影响用户体验;上述方案更简洁,且能从根源上解决状态不一致问题。

内容的提问来源于stack exchange,提问作者sujan basnet

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 06:08:15