Next13客户端与服务端Fetch结果不一致致水化错误求助
解决Next.js 13服务端与客户端Fetch结果不一致的水化错误
问题根源
你遇到的核心问题是:https://restcountries.com/v3.1/all API返回的国家列表没有固定排序规则,服务端渲染时发起的请求和客户端水化时发起的请求,拿到的数组顺序完全不同。这导致服务端生成的HTML结构和客户端hydrate时生成的DOM结构不匹配,触发了水化错误。
解决方案
方案1:强制缓存请求结果
通过指定fetch的缓存策略,让服务端和客户端复用同一份请求数据,确保两次渲染拿到的列表顺序一致:
export type CountryType = { name: string population: number region: string | undefined flagUrl: string | undefined capital: string | undefined } export default async function fetchData() { return fetch('https://restcountries.com/v3.1/all', { cache: 'force-cache', // 强制复用缓存数据 next: { revalidate: 86400 } // 缓存有效期1天,按需调整 }) .then((response) => response.json()) .then((countries) => { return countries.map((element: any): CountryType => { return { name: element.name.common, population: element.population, region: element.region, flagUrl: element.flags.png, capital: element.capital && element.capital[0], } }) }) }
方案2:对返回数据进行固定排序
不管API返回的顺序如何,手动对处理后的国家列表按固定规则排序(比如名称字母顺序),确保服务端和客户端渲染的列表顺序一致:
export default async function fetchData() { return fetch('https://restcountries.com/v3.1/all') .then((response) => response.json()) .then((countries) => { return countries.map((element: any): CountryType => { return { name: element.name.common, population: element.population, region: element.region, flagUrl: element.flags.png, capital: element.capital && element.capital[0], } }).sort((a, b) => a.name.localeCompare(b.name)); // 按国家名称字母排序 }) }
方案3:服务端获取数据后传递给客户端组件
将数据获取逻辑移到服务端组件,再把处理好的数据传递给客户端组件,从根源避免两次请求的差异:
- 重命名原客户端组件为
CountryListClient.tsx:
'use client' import CountryInput from '@/ui/CountryInput' import CountryItem from '@/ui/CountryItem' import SelectRegion from '@/ui/SelectRegion' import React from 'react' import { CountryType } from './fetchData' export default function CountryListClient({ countries }: { countries: CountryType[] }): JSX.Element { console.log(countries[0]) return ( <> <div className='w-full min-w-[315px] h-fit flex flex-col gap-y-12 px-4 items-center md:flex-row md:justify-between md:gap-x-2 md:px-16'> <CountryInput /> <SelectRegion /> </div> <div className='w-full min-w-[360px] h-fit flex flex-col gap-y-8 px-14'> {countries.map((element) => { return ( <CountryItem key={element.name} name={element.name} population={element.population} region={element.region} flagUrl={element.flagUrl} capital={element.capital} /> ) })} </div> </> ) }
- 新建服务端
Page.tsx组件:
import CountryListClient from './CountryListClient' import fetchData, { CountryType } from './fetchData' export default async function Page() { const countries: CountryType[] = await fetchData() return <CountryListClient countries={countries} /> }
关键说明
水化错误的核心要求是服务端渲染的HTML必须和客户端hydrate生成的DOM完全匹配,任何导致两次渲染数据不一致的因素(比如列表顺序、数据内容差异)都会触发错误。以上三种方案从缓存、数据处理、渲染架构三个层面消除了数据不一致的问题,任选其一即可解决你的问题。
内容的提问来源于stack exchange,提问作者Staniuu
相关产品推荐
相关产品推荐

