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

NextJS静态网站Hydration失败求助:服务端与客户端HTML不匹配

解决Next.js静态网站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 <img> in <div>.

问题代码

const JiuCafe = () => {
  const thumbnail = [
    {
      id: 1,
      img: "https://image.img",
    },
  ];

  const detail = [
    {
      name: "name",
      img: "https://image.img",
      description:
        "description",
    },
  ];

  return (
    <div className="shadow">
      <section id="services" className="services section-bg">
        <div className="container-fluid">
          <div className="row row-sm">
            <div className="col-md-6 _boxzoom">
              <div className="zoom-thumb">
                <ul className="piclist">
                  {thumbnail.map(({ img }) => (
                    <li>
                      <img src={img} alt=""></img>
                    </li>
                  ))}
                </ul>
              </div>
              <div className="_product-images">
                <div className="picZoomer">
                  {detail.map(({ img }) => (
                  <img className="my_img" src={img} alt=""></img>
                  ))}
                </div>
              </div>
            </div>
            <div className="col-md-6">
              <div className="_product-detail-content">
                {detail.map(({ name, description }) => (
                  <li>
                    <p className="_p-name">
                      <b>{name}</b>
                    </p>
                    <div className="_p-price-box">
                      <div className="_p-features">
                        <span> {description} </span>
                      </div>
                    </div>
                  </li>
                ))}
              </div>
            </div>
          </div>
        </div>
      </section>
    </div>
  );
};

问题分析与解决方法

这个Hydration不匹配问题的核心是服务端渲染的HTML和客户端首次渲染的DOM结构不一致,结合代码来看,需要修正以下几个点:

1. 给循环渲染的元素添加唯一key属性

使用map循环渲染元素时,必须给每个子元素添加唯一key,Next.js依赖这个属性匹配服务端和客户端的元素,缺失key极易导致DOM结构不匹配。

修改所有map循环的元素:

  • 缩略图列表:
{thumbnail.map(({ id, img }) => (
  <li key={id}>
    <img src={img} alt="" />
  </li>
))}
  • 主图部分:
{detail.map((item, index) => (
  <img key={index} className="my_img" src={item.img} alt="" />
))}
  • 详情列表:
{detail.map((item, index) => (
  <li key={index}>
    <p className="_p-name">
      <b>{item.name}</b>
    </p>
    <div className="_p-price-box">
      <div className="_p-features">
        <span> {item.description} </span>
      </div>
    </div>
  </li>
))}

(如果detail数组有唯一id,用id当key效果更好)

2. 修正JSX中img标签的写法

HTML写法的<img src={img} alt=""></img>在JSX里应改为自闭合标签<img src={img} alt="" />,虽然浏览器能兼容,但Next.js服务端渲染时可能因标签格式差异触发Hydration错误。

3. 用Next.js内置Image组件替换原生img

Next.js的Image组件会自动优化图片加载逻辑,避免服务端与客户端的渲染差异。首先导入组件:

import Image from 'next/image';

然后替换所有img标签:

  • 缩略图:
<Image src={img} alt="" width={80} height={80} />
  • 主图:
<div className="picZoomer" style={{ position: 'relative', width: '100%', height: '400px' }}>
  {detail.map((item, index) => (
    <Image 
      key={index}
      className="my_img" 
      src={item.img} 
      alt="" 
      fill 
      objectFit="contain" 
    />
  ))}
</div>

(fill属性需要父容器设置position: relative,这里直接给.picZoomer加了行内样式)

4. 修复语义化DOM结构

_product-detail-content里直接渲染<li>但没有包裹<ul>/<ol>,不符合HTML语义,也可能导致DOM解析差异,给详情列表加上父容器:

<div className="_product-detail-content">
  <ul>
    {detail.map((item, index) => (
      <li key={index}>
        {/* 原有内容不变 */}
      </li>
    ))}
  </ul>
</div>

完整修正后的代码示例

import Image from 'next/image';

const JiuCafe = () => {
  const thumbnail = [
    {
      id: 1,
      img: "https://image.img",
    },
  ];

  const detail = [
    {
      name: "name",
      img: "https://image.img",
      description: "description",
    },
  ];

  return (
    <div className="shadow">
      <section id="services" className="services section-bg">
        <div className="container-fluid">
          <div className="row row-sm">
            <div className="col-md-6 _boxzoom">
              <div className="zoom-thumb">
                <ul className="piclist">
                  {thumbnail.map(({ id, img }) => (
                    <li key={id}>
                      <Image src={img} alt="" width={80} height={80} />
                    </li>
                  ))}
                </ul>
              </div>
              <div className="_product-images">
                <div className="picZoomer" style={{ position: 'relative', width: '100%', height: '400px' }}>
                  {detail.map((item, index) => (
                    <Image 
                      key={index}
                      className="my_img" 
                      src={item.img} 
                      alt="" 
                      fill 
                      objectFit="contain" 
                    />
                  ))}
                </div>
              </div>
            </div>
            <div className="col-md-6">
              <div className="_product-detail-content">
                <ul>
                  {detail.map((item, index) => (
                    <li key={index}>
                      <p className="_p-name">
                        <b>{item.name}</b>
                      </p>
                      <div className="_p-price-box">
                        <div className="_p-features">
                          <span> {item.description} </span>
                        </div>
                      </div>
                    </li>
                  ))}
                </ul>
              </div>
            </div>
          </div>
        </div>
      </section>
    </div>
  );
};

export default JiuCafe;

内容的提问来源于stack exchange,提问作者Clement Chow

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 06:22:44