
按语:说实话,这篇文章读到一半我就感觉被暴击了——做技术博客这么久,SEO 检查从来没进过 CI/CD(持续集成/持续部署)流水线,全凭感觉和玄学。直到被产品经理指着流量报表质问,才意识到我们连最基本的关键词分布都没做验证。作者用两周时间同时跑两个工具跑出了一个 200 页的内容站,这种实操经验比任何官方文档都有说服力。翻译过程中我结合自己踩过的坑做了不少本地化调整,特别是把 Gatsby 换成了国内更常见的 Vue/Nuxt 场景,希望对写技术博客或者做内容站的朋友们有点帮助。
说实话,做技术博客这么多年,我踩过不少 SEO 的坑。最离谱的一次是我们花了半个迭代搭了个博客平台,结果有同事偶然发现——我们的核心关键词居然一个 H2 都没进。Google 知道,流量知道,就我们自己不知道。因为压根没有在任何流程里加 SEO 检查环节。
这件事逼着我花了两个礼拜研究 JavaScript 生态里的 SEO 工具。最后我直接在手上一个 200 页的 Nuxt 内容站里同时跑了两个工具,用的是真实数据,测的是生产环境。这篇文章就是我的真实感受——包括它们各自的软肋。
如果你用 Next.js、Remix 或者 Nuxt 开发,大概率在每次合并代码前都会跑 TypeScript 检查、代码 lint、单元测试。但 SEO 呢?通常是手动看两眼,甚至完全不看——直到 Google 已经索引了一个半成品页面才发现问题。
SEO 出问题通常在两个完全不同的阶段:
两个完全不同的问题,需要两个完全不同的工具。让我一个个说。
这里推荐 @power-seo/content-analysis。(先声明:我是这个库的维护者之一——所以你懂的,我的热情可能带点主观色彩,但好处是我对它的内部实现理解得比较透彻。)
这是一个 TypeScript 优先的库,能对你的内容字段跑 13 项页面级检查——包括 title、meta description、核心关键词、正文 HTML、slug、图片 Alt 属性——然后返回一个结构化的评分报告。
它可以在 Next.js 服务端组件、Remix loader、Vercel Edge Functions,还有普通的 Node.js 脚本里跑。不依赖 DOM,不依赖浏览器 API。
安装:
npm i @power-seo/content-analysis
一个真实的 MDX 博客预合并 CI(持续集成)门禁脚本:
// scripts/seo-gate.ts
import { analyzeContent } from '@power-seo/content-analysis';
import { readFileSync } from 'fs';
import matter from 'gray-matter';
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
async function runSeoGate(mdxFilePath: string): Promise<void> {
const raw = readFileSync(mdxFilePath, 'utf-8');
const { data: frontmatter, content: mdContent } = matter(raw);
const vfile = await unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeStringify)
.process(mdContent);
const result = analyzeContent({
title: (frontmatter.title as string) ?? '',
metaDescription: (frontmatter.description as string) ?? '',
focusKeyphrase: (frontmatter.focusKeyphrase as string) ?? '',
content: String(vfile),
slug: (frontmatter.slug as string) ?? '',
});
const failures = result.results.filter((r) => r.status === 'poor');
if (failures.length > 0) {
console.error('SEO gate failed:');
failures.forEach((f) => console.error(' X', f.description));
process.exit(1);
}
console.log(`SEO gate passed - Score: ${result.score}/${result.maxScore}`);
}
runSeoGate(process.argv[2]!);
实际检查内容包括: 关键词密度(0.5–2.5%)、关键词是否在开头段落、关键词是否至少出现在一个 H2 里、关键词是否在 slug 里、图片 Alt 覆盖率、Title 长度、meta description 长度等等。
在 GitHub Actions 里,PR 合并前跑这个:
# .github/workflows/seo-check.yml
name: SEO gate
run: npx ts-node scripts/seo-gate.ts content/posts/my-new-post.mdx
如果关键词在文章里分布不对,直接阻断构建。想再发一篇 Google 完全忽略的文章?没门了。
它的局限: 自定义规则加不了,这 13 项检查是固定的。如果你需要文章必须提到我们产品名称这种逻辑,对不起,这个库不负责,得自己写。
seo-analyzer 是另一种思路。它是个基于规则的 HTML 检查器——CLI 优先,可以跑文件、文件夹、URL,或者原始 HTML 字符串。它有 6 个内置规则,同时支持完全自定义的异步规则函数。
杀手级功能是 inputFolders()。构建完成后对着你的 /public 目录跑一遍,它自动扫描所有 HTML 文件。
安装:
npm i -D seo-analyzer
批量构建后审计 Nuxt 构建产物 /public 目录:
// scripts/bulk-audit.js
const SeoAnalyzer = require('seo-analyzer');
const { writeFileSync } = require('fs');
new SeoAnalyzer()
.inputFolders(['public'])
.ignoreFolders(['public/404', 'public/_nuxt'])
.addRule('titleLengthRule', { min: 50, max: 60 })
.addRule('imgTagWithAltAttributeRule')
.addRule('metaBaseRule', { list: ['description', 'viewport'] })
.addRule('canonicalLinkRule')
.addRule('aTagWithRelAttributeRule')
.outputJson((json) => {
writeFileSync('seo-report.json', json);
console.log('Report written to seo-report.json');
})
.run();
或者完全不用写脚本,直接用 CLI:
seo-analyzer -fl public
一条命令扫描 200 个 HTML 文件,暴露所有结构性问题。部署后 CI 流程里用这个,绝杀。
自定义规则才是 seo-analyzer 真正发挥作用的地方。假设你需要每个页面都有 WebPage JSON-LD 结构化数据:
const jsonLdRule = async (dom) => {
const scripts = dom.window.document.querySelectorAll(
'script[type="application/ld+json"]'
);
const hasWebPage = Array.from(scripts).some((s) => {
try {
return JSON.parse(s.textContent)['@type'] === 'WebPage';
} catch {
return false;
}
});
return hasWebPage ? [] : ['Missing WebPage JSON-LD structured data'];
};
new SeoAnalyzer()
.inputFolders(['public'])
.addRule(jsonLdRule)
.outputObject(console.log)
.run();
8 行代码搞定,不需要升级任何库版本。
它的局限: 没有核心关键词概念,只检查结构——Title 长度、canonical 是否存在、Meta 标签有没有——但完全无法告诉你关键词有没有出现在 H2 或者开头段落里。而且它是 CommonJS(同步模块规范),完全没有 TypeScript 类型。
测试环境:MacBook Pro M2,Node.js 20:
包体积也很重要。@power-seo/content-analysis 压缩后大概 60KB,是 ESM 模块,支持 tree-shaking,边缘运行时安全。seo-analyzer 约 1MB,而且带了 Node.js 专属依赖。千万别让它进前端打包里。
如果想了解关键词评分这套思路,Power SEO 生态(包括 @power-seo/content-analysis)都是开源的:Power SEO
我跟不少团队聊过,大多数要么 CI 里完全没有 SEO 检查,要么只在部署后扫一下 URL。真正在合并前跑内容门禁的,少之又少。
你们的 SEO 验证流水线是怎么设计的? CI 里跑?编辑器里跑?还是两者都没有,全靠上线后去 Search Console(谷歌搜索控制台)看数据亡羊补牢?我很好奇关键词级别的验证是不是团队真正需要的,还是结构级检查对大多数场景就够用了。
评论区聊聊你们现在的配置——我是真的想了解。