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

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属性抑制警告)。

修改步骤:

  1. 在getStaticProps里移除Marked的highlight配置:
// 删掉这段配置
// marked.setOptions({
//     highlight: (code, lang) => {
//         if (prism.languages[lang]) {
//             return prism.highlight(code, prism.languages[lang], lang);
//         } else {
//             return code;
//         }
//     },
// });
  1. 保留客户端的useEffect高亮代码,并给代码块容器添加suppressHydrationWarning:
<div className="rich-article" dangerouslySetInnerHTML={{ __html: content }} suppressHydrationWarning />

注意事项

  • 方案1更符合SSG的特性,服务端一次性生成完整内容,客户端直接复用,性能更好也不会有警告。
  • 确保服务端导入了所有需要的PrismJS语言组件,避免某些语言在服务端无法高亮导致内容差异。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 22:01:18