图片 alt 文本批量缺失:几百张图、零无障碍——审计、回填、MDX lint 锁死

img 标签无 alt——无障碍砸了、图片搜索没法索引、AdSense 质量信号差。审计、回填、prebuild 强制 + MDX lint 锁死。

跑一次 accessibility 审计——报告很难看:180 篇文章 340 张图无 alt,或更糟——alt="" 当占位符随手贴。屏幕阅读器念”image”或者悄悄跳过。Google 图片搜索没东西索引——AdSense 质量审核看到懒散作者。光是无障碍这一击就够动手;SEO 和政策含义把它推到紧急区。

写文章时根本没强制 alt——多数图被拖进编辑器、alt 字段空着。修法机械:grep-and-fix 扫一遍、prebuild 缺 alt 就 fail、MDX lint 规则——未来 PR 无法回退。一次做完锁死,比每次审计都重做便宜得多。

常见原因

1. 编辑器从未强制 alt

Markdown 编辑器(或随便什么)接受 ![](image.png) 不抱怨——作者懒得填、没摩擦。

如何判断:grep 所有 MDX 找空 alt 的 ![]

grep -rEn '!\[\]\(' src/content/articles/ | wc -l

2. HTML img 标签没 alt

作者要尺寸控制改用裸 <img>,忘了 alt:<img src="x.png" width="600" /> 是合法 HTML 但无障碍坏了。

如何判断:grep 没 alt 属性的 img。

grep -rEn '<img [^>]*src=' src/content/articles/ | grep -v 'alt='

3. alt 存在但是装饰性占位

作者写 alt="image"alt="screenshot" 来骗审计——技术上有、语义上没用。

如何判断:grep 已知坏模式。

grep -rEn 'alt="(image|screenshot|picture|img|photo)"' src/content/articles/

4. 老 CMS 批量导入剥了 alt

从 WordPress / Notion / Ghost 迁移——导出格式丢了 alt 或存在导入器忽略的兄弟字段——所有迁来的图都无 alt。

如何判断:查迁移日期——那天前的文章都是受影响群体。

5. 装饰性图——alt="" 才是对的

有些图纯装饰(分隔线、通用插画)——这些用 alt=""——屏幕阅读器应跳过。审计必须区分”缺 alt”和”故意空 alt”。

如何判断:每个空 alt 个例审一遍——要么填、要么标记装饰。

最短修复路径

Step 1:把缺口盘点出来

按严重度分组的报告:

# scripts/audit-image-alt.mjs
import fs from "node:fs";
import path from "node:path";

const dirs = [
  "src/content/articles/en",
  "src/content/articles/zh",
];

const issues = { missingMarkdown: [], missingHtml: [], placeholder: [] };

function scan(dir) {
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    const p = path.join(dir, entry.name);
    if (entry.isDirectory()) { scan(p); continue; }
    if (!p.endsWith(".mdx")) continue;
    const txt = fs.readFileSync(p, "utf8");
    if (/!\[\]\(/.test(txt)) issues.missingMarkdown.push(p);
    if (/<img(?![^>]*alt=)/.test(txt)) issues.missingHtml.push(p);
    if (/alt="(image|screenshot|picture|img|photo)"/i.test(txt)) issues.placeholder.push(p);
  }
}
scan("src/content/articles");
console.log(JSON.stringify(issues, null, 2));

现在你有精确的违规清单和类别。

Step 2:按流量回填最严重的

高流量文章——手写真 alt。低流量——模板化(“X dashboard 显示 Y 的截图”)或标装饰。不要一口气 AI 生 340 条——读者会注意到通用措辞。

合理分级:

- 高流量(按印象 top 50):手写
- 中流量:AI 生、20 个一批人审
- 低流量 / 装饰:alt="" 加代码注释标"故意"

Step 3:prebuild 检查缺 alt 直接 fail

把审计接到 prebuild、非零退出:

# package.json
"scripts": {
  "audit:alt": "node scripts/audit-image-alt.mjs",
  "prebuild": "npm run audit:content && npm run audit:alt && ..."
}

必须区分缺 alt故意空 alt——约定:空 alt 只在前一行有 {/* decorative */} 注释标记或匹配已知装饰资产路径模式时才合法。

Step 4:加 MDX lint 规则

持续强制——用 remark-lint-no-empty-image-alt-text + 自定义规则配 HTML img:

// .remarkrc.mjs
export default {
  plugins: [
    "remark-lint",
    ["remark-lint-no-empty-image-alt-text", "warn"],
    // 自定义规则配裸 <img>
    () => (tree, file) => {
      visit(tree, "html", node => {
        if (/<img(?![^>]*alt=)/.test(node.value)) {
          file.message("img tag missing alt attribute", node);
        }
      });
    },
  ],
};

在动 .mdx 的 PR 跑 CI。

Step 5:写下约定

加一段简短的作者指南:

- 真 alt:描述图显示什么、AND 在上下文里为什么重要
- 装饰图:alt="" + 前一行 {/* decorative */} 注释
- 永不用"image"/"screenshot"这类占位字符串
- UI 截图:app 名 + feature 名 + 当前相关状态

未来作者有一处可查、reviewer 有一条规则要执行。

预防

  • 任何 ![]<img> 缺 alt——prebuild fail,零例外
  • MDX lint 规则在 PR 时抓
  • 作者指南 alt 段从 PR 模板里链
  • 装饰图用显式 alt="" + 注释标记
  • 季度审计重跑脚本——报告零回退
  • 迁移脚本(如有)上线前验证 alt 被保留

相关

标签: #内容运营 #站点质量 #站点审计 #排查 #alt-text