使用getServerSideProps时Hydration失败:Urql与next-i18next SSR问题排查
问题排查:Urql SSR与next-i18next结合的Hydration错误
问题背景
我使用Urql客户端向GraphQL服务器发起查询,初始代码运行正常:
const username = () => { const { t: translation } = useTranslation(["common", "profile"]); const { query, locale } = useRouter(); const username = query.username; const [{ data: userProfileData }] = useUserProfileQuery({ variables: { username: username as string, }, pause: isServer || !username, }); const userProfile = userProfileData?.userProfile; return ( <Text>{userProfile.name}</Text> ); }; export default withUrqlClient(createUrqlClient, { ssr: false })(username);
为了用next-i18next实现国际化,必须添加getServerSideProps,但这导致无法通过ssr: true开启服务端渲染,我的目标是让useUserProfileQuery在服务端执行查询。
按照Urql SSR指南修改getServerSideProps后:
export const getServerSideProps = async ({ locale, locales, query }: any) => { const ssrCache = ssrExchange({ isClient: false }); const client = initUrqlClient(createUrqlClient(ssrCache), false); if (client) await client .query(UserProfileDocument, { username: query.username, }) .toPromise(); return { props: { ...(await serverSideTranslations( locale, ["common", "profile", "settings"], null, locales )), urqlState: ssrCache.extractData(), }, }; };
出现以下Hydration错误:
Error: Hydration failed because the initial UI does not match what was rendered on the server. Warning: Expected server HTML to contain a matching <div> in <div>
错误原因
- 服务端与客户端数据不同步:服务端通过
initUrqlClient预取了数据,但客户端未利用urqlState初始化缓存,导致初始渲染无数据,和服务端HTML输出不一致。 pause参数逻辑错误:原代码中pause: isServer || !username导致服务端渲染组件时查询被暂停,无法读取预取的缓存数据,服务端输出空内容,客户端hydration时拿到数据后渲染出内容,引发不匹配。- 未处理空数据情况:直接访问
userProfile.name,若服务端预取失败或客户端初始无数据,会导致渲染内容差异。
修复步骤
1. 开启withUrqlClient的SSR模式
将ssr: false改为ssr: true,让客户端自动读取服务端传来的urqlState初始化缓存:
export default withUrqlClient(createUrqlClient, { ssr: true })(username);
2. 调整pause条件
服务端已预取数据,组件渲染时无需暂停查询,仅在客户端无username时暂停:
const [{ data: userProfileData }] = useUserProfileQuery({ variables: { username: username as string, }, pause: !isServer && !username, // 服务端不暂停,客户端无username时暂停 });
3. 为空数据添加兜底
避免因userProfile为undefined导致渲染内容差异:
return ( <Text>{userProfile?.name || ''}</Text> );
4. 确保createUrqlClient正确集成ssrExchange
检查createUrqlClient函数是否将传入的ssrCache加入exchanges数组:
export const createUrqlClient = (ssrExchange?: any) => ({ url: '你的GraphQL服务地址', exchanges: [ dedupExchange, cacheExchange, ...(ssrExchange ? [ssrExchange] : []), fetchExchange, ], });
5. 优化getServerSideProps的查询逻辑
确保仅在存在username时执行查询,避免无效请求:
export const getServerSideProps = async ({ locale, locales, query }: any) => { const ssrCache = ssrExchange({ isClient: false }); const client = initUrqlClient(createUrqlClient(ssrCache), false); if (client && query.username) { await client .query(UserProfileDocument, { username: query.username, }) .toPromise(); } return { props: { ...(await serverSideTranslations( locale, ["common", "profile", "settings"], null, locales )), urqlState: ssrCache.extractData(), }, }; };
内容的提问来源于stack exchange,提问作者jmecs
相关产品推荐
相关产品推荐

