
终于解决了博客网站一个特别隐蔽的Bug
项目:jianglai520.com(Next.js 16 + Tailwind CSS 4 + TipTap)
行内代码反引号 Bug 分析报告
项目:jianglai520.com(Next.js 16 + Tailwind CSS 4 + TipTap) 日期:2026-11-09 结论:HTML 完全正常,反引号由
@tailwindcss/typography的 CSS 伪元素生成
一、问题现象
文章正文里的行内代码,例如:
请读取 `read_file` 的内容。预期显示:
请读取
read_file的内容。(
read_file是等宽字体 + 淡青底的行内代码块,不显示反引号)
实际显示:
请读取
`read_file`的内容。(反引号原样出现在页面上)
影响范围:所有含行内代码的文章,不是某一篇。
二、为什么这个 Bug 很隐蔽
这个问题的迷惑性在于:从三个层面看,代码都是"正确"的。
| 检查层 | 结果 | 容易得出的错误结论 |
|---|---|---|
| 数据库原始内容 | `read_file` 是正常 Markdown 反引号 |
"数据没问题" |
| 服务端渲染 HTML | <code class="language-text">read_file</code> |
"渲染器没问题" |
| 全量抓取页面扫描 | 可见正文里反引号字符数量为 0 | "用户看到的是缓存" |
| 浏览器实际显示 | 反引号可见 | ← 真实问题 |
关键点:反引号不在 HTML 里,也不在数据库里,而是浏览器根据 CSS 在渲染时"画"上去的。
抓 curl、看页面源码、用爬虫、扫 HTML,都看不到它。
三、排查过程(真实时间线)
1. 初始误判:以为是 Markdown 渲染器不支持
检查 app/components/Markdown.tsx:
- 使用
remark-parse → remark-gfm → remark-rehype → rehype-stringify - 支持行内代码、表格、代码块
- 结论:渲染管线正常
2. 检查数据库
查询 posts.content,发现:
- 行内代码是正常反引号(
`read_file`),不是转义字符 - 但发现另一个独立问题:部分表格被压平成段落(TipTap 编辑器不支持表格)
3. 抓线上 HTML
curl -sL https://jianglai520.com/posts/50 | grep -o '<code[^>]*>[^<]*</code>'输出:
<code class="language-text">read_file</code>HTML 完全正常,没有任何反引号。
4. 全量扫描 14 篇文章
去掉代码块后统计可见正文里的 `、**、##、|---:
fences: 0, ticks: 0, stars: 0, hashes: 0, tableSep: 0结论:所有文章正文 HTML 都是干净的。
5. 转折点:区分「内容层」和「表现层」
用户坚持"页面上就是有反引号"。重新思考:
HTML 正常,但显示不正常 → 一定是浏览器渲染阶段加了东西。
浏览器渲染阶段可能加东西的来源:
- CSS
::before/::after伪元素 - 浏览器扩展
- 字体连字(ligature)
- 用户代理默认样式
6. 定位到 Tailwind Typography
检查项目依赖:Markdown 容器用了:
<div className="markdown-body prose prose-invert max-w-none" ... />prose 来自 @tailwindcss/typography。查看插件源码:
// node_modules/@tailwindcss/typography/src/styles.js
code: {
color: 'var(--tw-prose-code)',
fontWeight: '600',
},
'code::before': {
content: '"`"', // ← 元凶
},
'code::after': {
content: '"`"', // ← 元凶
},Typography 默认会给所有行内代码加反引号装饰。
四、根因
@tailwindcss/typography 的 prose 类默认样式:
.prose code::before { content: "`"; }
.prose code::after { content: "`"; }浏览器渲染 <code>read_file</code> 时:
实际 DOM: <code>read_file</code>
浏览器显示: `read_file`
↑ ↑
伪元素 伪元素所以:
- 数据库里没有多余反引号
- HTML 里没有多余反引号
- 反引号是 CSS 伪元素
content生成的
为什么代码块不受影响?
Typography 对 pre code 单独做了重置:
'pre code::before': { content: 'none' },
'pre code::after': { content: 'none' },所以只有行内代码会出现反引号。
五、修复方案
在 app/globals.css 增加覆盖:
/* 去掉 @tailwindcss/typography 自动给行内代码加的反引号(::before/::after) */
.markdown-body code::before,
.markdown-body code::after {
content: none;
}为什么这样能覆盖?
| 规则 | 特异性 |
|---|---|
.prose :where(code):not(:where([class~="not-prose"] *))::before |
(0, 1, 0)(:where 不增加特异性) |
.markdown-body code::before |
(0, 1, 1) |
后者特异性更高,且不依赖 CSS 加载顺序,稳定生效。
修复后效果
请读取 `read_file` 的内容。显示为:
请读取
read_file的内容。(
read_file显示为行内代码块,不再有可见反引号)
六、验证方式
1. 构建产物验证
npm run build
grep -o "markdown-body code[^}]*}" .next/static/css/*.css输出包含:
.markdown-body code:before,.markdown-body code:after{content:none}2. 浏览器 DevTools 验证(推荐)
打开任意文章,F12 → Elements:
- 选中行内代码
<code> - 在 Styles 面板查看
::before/::after - 修复前:
content: "\`" - 修复后:
content: none
或在 Console 执行:
getComputedStyle(
document.querySelector(".markdown-body code"),
"::before"
).content- 修复前:
"`" - 修复后:
none/normal
七、为什么之前会误判(复盘)
这次排查走了弯路,核心原因有三个:
1. 把「HTML 正确」等同于「显示正确」
HTML 只是内容层。最终显示还受:
- CSS 伪元素(
::before/::after) content属性- 字体连字
- 浏览器扩展
影响。
2. 两个问题混在一起
同期还存在另一个独立问题:TipTap 编辑器把 Markdown 表格压平。两个问题都表现为"页面上出现原始 Markdown 符号",容易混为一谈:
| 问题 | 根因 | 修复 |
|---|---|---|
| 表格压平 | 编辑器不支持表格,保存时结构丢失 | 源码模式 + 批量修复数据 |
| 行内代码反引号 | Typography 的 CSS 伪元素 | 一条 CSS 覆盖 |
3. 全量扫描的盲区
扫描的是HTML 文本,天然看不到 CSS 伪元素生成的内容。扫描结果"0 个反引号"反而强化了"页面没问题"的错误判断。
八、通用排查清单(以后遇到类似问题)
当"页面显示和 HTML 不一致"时,按这个顺序查:
- 看 HTML:目标元素是否正确?
curl -sL <url> | grep -o '<code[^>]*>[^<]*</code>' - 看 DevTools Elements:元素上有没有
::before/::after? - 看 computed style:
getComputedStyle(el, "::before").content - 搜索依赖的默认样式:
grep -rn "code::before\|content:" node_modules/@tailwindcss/typography/src/styles.js - 检查特异性:覆盖规则是否足够强?
- 最后才怀疑缓存:缓存通常影响整页,不会只影响行内代码。
九、经验总结
核心教训
HTML 对 ≠ 显示对。 内容正确只代表"内容层"正确,最终呈现还隔着一层 CSS。
具体经验
- Tailwind Typography 的
prose自带很多装饰:行内代码反引号、引用引号、表格边框等。接入自定义设计系统时,要有意识地逐项覆盖。 - 伪元素是排查盲区:
curl、爬虫、页面源码、RSC payload 都看不到::before/::after的content。 - 不要用"扫描 HTML 没发现"来否定用户反馈:用户看到的是浏览器渲染结果,不是 HTML。
- 先分离问题再修复:同期多个渲染问题要分别定位根因,避免"修了一个以为解决了全部"。
一句话版本
行内代码的反引号不是 Markdown 语法残留,而是 Tailwind Typography 的
code::before/after伪元素;覆盖content: none即可。
附:修复前后对照
修复前
<!-- HTML:正常 -->
<code class="language-text">read_file</code>
<!-- 浏览器实际显示: -->
`read_file`修复后
.markdown-body code::before,
.markdown-body code::after {
content: none;
}<!-- HTML:不变 -->
<code class="language-text">read_file</code>
<!-- 浏览器实际显示: -->
read_file评论 (0)
还没有评论,来写第一条吧~