Next.js中React Hydration错误求助:服务端HTML缺少匹配Button
问题描述
在Next.js项目中使用@nextui-org/react的自定义分页和卡片组件时,本地运行正常,但部署到生产环境后出现Hydration失败错误:
Hydration failed because the initial UI does not match what was rendered on the server.
同时伴随DOM匹配警告:
Expected server HTML to contain a matching
<button>in<div>.
相关组件代码如下:
import React, { useState } from "react"; import { Pagination as NextUIPagination } from "@nextui-org/react"; import { Card as NextUICard, CardHeader, CardFooter, Image, Button } from "@nextui-org/react"; // Interface for Pagination component props interface PaginationProps { total: number; currentPage: number; onPageChange: (page: number) => void; } // Pagination component const Pagination: React.FC<PaginationProps> = ({ total, currentPage, onPageChange }) => { return ( <NextUIPagination key="pagination" showControls total={total} initialPage={currentPage} onChange={onPageChange} aria-label="Pagination Navigation" /> ); }; // Interface for Card component props interface CardProps { location: string; description: string; price: string; rating: string; image: string; href: string; } // Card component const Card: React.FC<CardProps> = ({ location, description, price, rating, image, href }) => { const [svgColor, setSvgColor] = useState<string | undefined>(undefined); const toggleSvgColorChange = () => { setSvgColor((prevColor) => (prevColor === 'red' ? undefined : 'red')); }; return ( <> <NextUICard key={href} isPressable radius="lg" className="border-none bg-opacity-0 backdrop-blur-lg" shadow="md" > <Image isBlurred isZoomed alt={description} src={image} className="object-cover" /> <CardHeader className="flex justify-end overflow-hidden py-1 absolute top-2 w-[calc(100%_-_8px)]"> <Button isIconOnly variant="light" color="default" size="sm" className="before:bg-white/10 border-white/20 border-1 before:rounded-xl shadow-small" onClick={toggleSvgColorChange} aria-label="Favorite" > <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill={svgColor || "currentColor"} className="w-5 h-5 text-default-500"> <path d="m9.653 16.915-.005-.003-.019-.01a20.759 20.759 0 0 1-1.162-.682 22.045 22.045 0 0 1-2.582-1.9C4.045 12.733 2 10.352 2 7.5a4.5 4.5 0 0 1 8-2.828A4.5 4.5 0 0 1 18 7.5c0 2.852-2.044 5.233-3.885 6.82a22.049 22.049 0 0 1-3.744 2.582l-.019.01-.005.003h-.002a.739.739 0 0 1-.69.001l-.002-.001Z"/> </svg> </Button> </CardHeader> <CardFooter className="gap-1.5 flex-col items-start"> <div className="w-full flex justify-between"> <p key="location">{location}</p> <div className="flex flex-row gap-1.5 items-center"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="w-4 h-4 text-default-400"> <path fillRule="evenodd" d="M10.868 2.884c-.321-.772-1.415-.772-1.736 0l-1.83 4.401-4.753.381c-.833.067-1.171 1.107-.536 1.651l3.62 3.102-1.106 4.637c-.194.813.691 1.456 1.405 1.02L10 15.591l4.069 2.485c.713.436 1.598-.207 1.404-1.02l-1.106-4.637 3.62-3.102c.635-.544.297-1.584-.536-1.65l-4.752-.382-1.831-4.401Z" clipRule="evenodd"/> </svg> <p className="text-default-400 text-sm" key="rating">{rating}</p> </div> </div> <p className="text-sm text-default-500 text-left" key="description">{description}</p> <p className="text-sm text-default-400" key="price">{price}</p> </CardFooter> </NextUICard> </> ); }; export { Pagination, Card };
解决方案
问题核心是服务端渲染的DOM结构与客户端Hydration后的DOM结构不一致,以下是针对性修复方案:
1. 延迟客户端状态初始化
Card组件中useState初始化的svgColor会导致服务端与客户端渲染差异——服务端无状态,客户端Hydration时会立即应用初始状态,和NextUI服务端渲染的输出冲突。
修复:用useEffect将状态初始化延迟到客户端挂载后:
import React, { useState, useEffect } from "react"; // 在Card组件内部修改状态逻辑 const Card: React.FC<CardProps> = ({ ...props }) => { const [svgColor, setSvgColor] = useState<string | undefined>(undefined); // 仅在客户端挂载后初始化状态 useEffect(() => { setSvgColor(undefined); // 保持原初始值,仅延迟到客户端执行 }, []); // ... 其余代码不变 };
2. 禁用组件的SSR渲染
如果NextUI组件本身存在SSR兼容性问题,可使用Next.js的dynamic导入组件并禁用SSR:
import dynamic from 'next/dynamic'; // 动态导入组件,禁用服务端渲染 const Pagination = dynamic(() => import('./path-to-your-component').then(mod => mod.Pagination), { ssr: false }); const Card = dynamic(() => import('./path-to-your-component').then(mod => mod.Card), { ssr: false });
3. 移除多余的Fragment包裹
Card组件外层的空Fragment(<>...</>)可能导致DOM结构的细微差异,直接返回NextUICard即可:
// 修改前 return ( <> <NextUICard ...>...</NextUICard> </> ); // 修改后 return ( <NextUICard ...>...</NextUICard> );
4. 确保Props的一致性
检查传入组件的所有props(如location、image等)在服务端和客户端是否完全一致,避免因数据差异导致UI渲染不同。
5. 更新NextUI版本
确保使用的@nextui-org/react是最新版本,新版本通常会修复SSR相关的DOM匹配问题。
内容的提问来源于stack exchange,提问作者user20983853
相关产品推荐
相关产品推荐

