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

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:服务端获取数据后传递给客户端组件

将数据获取逻辑移到服务端组件,再把处理好的数据传递给客户端组件,从根源避免两次请求的差异:

  1. 重命名原客户端组件为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>
        </>
    )
}
  1. 新建服务端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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 09:02:53