NextJS SSG结合PrismJS渲染代码块时出现dangerouslySetInnerHTML不匹配警告
解决Next.js SSG中Marked+PrismJS渲染Markdown的
dangerouslySetInnerHTML不匹配警告 我在使用Next.js静态站点生成(SSG)、Marked和PrismJS渲染带代码语法高亮的Markdown内容时,始终收到这条警告:
Warning: Prop 'dangerouslySetInnerHTML' did not match. Server: ... Client: ...
虽然代码能正常运行,但警告一直存在,推测是服务端与客户端生成的HTML不一致导致的,不知道怎么在保留PrismJS语法高亮的同时消除这个问题。
当前代码([slug].tsx)
import fs from "fs"; import Head from "next/head"; import { parseMarkdown } from "../../components/parseMarkdown"; import { ArticleMeta } from "../../components/ArticleMeta"; import { markdownParser } from "../../components/utils/RichContent/Markdown"; import { ImageComponent } from "../../components/ImageComponent"; import { findDirectoryOfPost, getBlogArticleNames } from "../../components/getBlogArticles"; import { ShareOnSocials } from "../../components/ShareOnSocials"; import prism from "prismjs"; import { useEffect } from "react"; import { marked } from "marked"; import 'prismjs/components/prism-css'; import 'prismjs/components/prism-javascript'; import 'prismjs/components/prism-typescript'; import 'prismjs/components/prism-jsx'; import 'prismjs/components/prism-tsx'; import "prismjs/themes/prism-tomorrow.css"; const ArticlePage = ({ metadata, content }: any) => { const title = `${metadata.title} • Charow`; useEffect(() => { if (typeof window !== "undefined") { prism.highlightAll(); } }, []); return ( <div className="article-page"> <main> <section className="article-page__content container"> <div className="article container--700"> <div className="rich-article" dangerouslySetInnerHTML={{ __html: content }} /> </div> </section> </main> </div> ); }; export const getStaticPaths = async () => { const files = getBlogArticleNames(); return { paths: files.map((filename) => ({ params: { articleSlug: filename.replace(".md", ""), }, })), fallback: false, }; }; export const getStaticProps = async ({ params: { articleSlug } }: any) => { const directory = findDirectoryOfPost(articleSlug); const fileContent = fs.readFileSync(`posts/${directory}/` + articleSlug + ".md").toString(); const currentArticle = parseMarkdown(fileContent); marked.setOptions({ highlight: (code, lang) => { if (prism.languages[lang]) { return prism.highlight(code, prism.languages[lang], lang); } else { return code; } }, }); const parsedMarkdown = marked(currentArticle.content); return { props: { metadata: currentArticle.metadata, content: parsedMarkdown, }, }; }; export default ArticlePage;
问题原因
当前的核心矛盾是:服务端已经通过Marked的highlight配置调用PrismJS生成了带完整高亮样式的HTML代码,但客户端又在useEffect里调用prism.highlightAll()重新对代码块进行高亮处理。这会导致客户端最终生成的HTML结构(比如额外添加的class或DOM元素)和服务端渲染的不一致,触发Next.js的Hydration不匹配警告。
解决方案
方案1:移除客户端重复高亮(推荐)
既然服务端已经生成了完整的高亮HTML,客户端不需要再重复执行高亮逻辑,直接删掉useEffect里的代码即可。这样客户端会直接复用服务端渲染的HTML,完全避免不匹配问题。
修改后的组件代码:
const ArticlePage = ({ metadata, content }: any) => { const title = `${metadata.title} • Charow`; // 移除了之前的useEffect高亮代码 return ( <div className="article-page"> <main> <section className="article-page__content container"> <div className="article container--700"> <div className="rich-article" dangerouslySetInnerHTML={{ __html: content }} /> </div> </section> </main> </div> ); };
方案2:统一两端逻辑,仅在客户端高亮(可选)
如果不想在服务端处理高亮,可以修改服务端的Marked配置,不启用高亮,只生成原始代码块,然后客户端在useEffect里执行高亮。这种方式需要注意添加加载状态避免闪烁,但仍可能出现短暂的Hydration不匹配(不过可以通过suppressHydrationWarning属性抑制警告)。
修改步骤:
- 在
getStaticProps里移除Marked的highlight配置:
// 删掉这段配置 // marked.setOptions({ // highlight: (code, lang) => { // if (prism.languages[lang]) { // return prism.highlight(code, prism.languages[lang], lang); // } else { // return code; // } // }, // });
- 保留客户端的
useEffect高亮代码,并给代码块容器添加suppressHydrationWarning:
<div className="rich-article" dangerouslySetInnerHTML={{ __html: content }} suppressHydrationWarning />
注意事项
- 方案1更符合SSG的特性,服务端一次性生成完整内容,客户端直接复用,性能更好也不会有警告。
- 确保服务端导入了所有需要的PrismJS语言组件,避免某些语言在服务端无法高亮导致内容差异。
内容的提问来源于stack exchange,提问作者Rowin_nb2
相关产品推荐
相关产品推荐

