Next.js 15使用shadcn-ui Card组件出现Hydration不匹配错误求助
解决Next.js 15中shadcn-ui Card组件的Hydration不匹配错误
核心问题分析
你遇到的「Hydration failed」错误本质是服务端渲染的HTML与客户端渲染的DOM结构不一致,结合代码来看,主要有以下几个触发点:
解决方案
1. 误用Lucide的Link组件作为路由链接
你当前导入的Link来自lucide-react,这是图标组件而非Next.js的路由跳转组件,将Card包裹在这个图标组件内会生成无效的DOM结构,直接导致服务端与客户端渲染结果不匹配。
修改方式:
替换路由Link组件,改用Next.js官方的next/link:
// 替换原导入语句 import Link from "next/link"; // 如果需要Link图标,单独导入并改名 // import { Link as LinkIcon } from "lucide-react";
同时给Next.js的Link添加block类,确保它正确包裹整个Card:
<Link href={`/blog/${post.metadata.category}/${post.slug}`} key={post.slug} className="block" > <CardCategory title={post.metadata.title} summary={post.metadata.summary} date={post.metadata.publishedAt} /> </Link>
2. 修复notFound()的判断逻辑
filter方法返回的是空数组而非null,原代码中if (!posts)永远不会触发,导致没有对应分类的文章时,服务端渲染空页面,客户端却不触发404,造成内容差异。
修改方式:
// 原判断 // if (!posts) { notFound(); } // 改为判断数组长度 if (posts.length === 0) { notFound(); }
3. 统一日期排序逻辑
直接用new Date()比较日期,可能因服务端与客户端的时区差异导致排序结果不同,进而使DOM元素顺序不一致。
修改方式:
使用Date.parse()统一日期解析逻辑,确保排序结果一致:
const sortedPosts = [...posts].sort((a, b) => Date.parse(b.metadata.publishedAt) - Date.parse(a.metadata.publishedAt) );
建议直接在getBlogPosts函数中完成排序,减少组件内的动态逻辑,进一步避免 hydration 差异。
4. 检查Card组件的客户端依赖(可选)
如果shadcn-ui的Card组件包含客户端专属API(如useEffect、useState),需要在CardCategory组件顶部添加客户端指令:
"use client"; // 仅当组件需要客户端API时添加 import { Card, CardContent, CardFooter, CardHeader, CardTitle, } from "./ui/card"; // ... 其余代码不变
修改后的完整页面代码示例
import CardCategory from "@/components/CardCategory"; import Container from "@/components/Container"; import Link from "next/link"; import { notFound } from "next/navigation"; import { use } from "react"; import { getBlogPosts } from "../utils"; export function generateStaticParams() { let posts = getBlogPosts(); // 去重分类,避免生成重复的静态路由参数 const uniqueCategories = [...new Set(posts.map(post => post.metadata.category))]; return uniqueCategories.map((category) => ({ category, })); } export async function generateMetadata({ params, }: { params: Promise<{ category: string }>; }) { let { category } = await params; return { title: category.toLocaleUpperCase(), description: `All articles regarding ${category}`, // 修正拼写错误reagarding -> regarding }; } export default function Page({ params, }: { params: Promise<{ category: string }>; }) { let { category } = use(params); let posts = getBlogPosts().filter( (post) => post.metadata.category === category ); if (posts.length === 0) { notFound(); } const sortedPosts = [...posts].sort((a, b) => Date.parse(b.metadata.publishedAt) - Date.parse(a.metadata.publishedAt) ); return ( <> <Container> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mt-10"> {sortedPosts.map((post) => ( <Link href={`/blog/${post.metadata.category}/${post.slug}`} key={post.slug} className="block" > <CardCategory title={post.metadata.title} summary={post.metadata.summary} date={post.metadata.publishedAt} /> </Link> ))} </div> </Container> </> ); }
内容的提问来源于stack exchange,提问作者Gökhan Ekici
相关产品推荐
相关产品推荐

