审计 tag 页——23 个显示”No articles found”。它们用的 tag 来自你后来删掉的文章、或从未发布的草稿。空页还在渲染、还在 sitemap 里、Google 还在爬——Search Console 标为”Crawled — currently not indexed”或更糟”Soft 404”。对用户毫无帮助、还在拉低站点质量信号。
空 tag 页是任何内容清理的天然副产物。修法两层:回填(审计存量空 tag、去索引或 410)+ 预防(prebuild 规则:tag 必须至少有 N 篇已发布非草稿才渲染)。
常见原因
1. 用了某 tag 的文章被删
批量删了薄文——其中有些是某 tag 的唯一用户——那个 tag 归档页现在 0 内容。
如何判断:列 frontmatter 用到的所有 tag、查文章计数。
# scripts/count-tags.mjs
import fs from "node:fs";
import matter from "gray-matter";
const counts = new Map();
for (const f of /* 遍历文章 */ []) {
const { data } = matter(fs.readFileSync(f, "utf8"));
if (data.draft) continue;
for (const t of (data.tags || [])) {
counts.set(t, (counts.get(t) || 0) + 1);
}
}
// 然后和 tag config / generator 中声明的 tag 比
tag config 里 count 0 的——orphan。
2. 用某 tag 的文章全被设草稿
一次质量行动把某些文章批量设草稿——剩下的 tag 0 篇已发布——tag 页渲染”无文章”空态。
如何判断:同脚本——计数时排除 draft: true。
3. tag 分类有拼写制造重复
你有 ai-tools 和 ai-tool 两个 tag——复数有文章、单数没(或反过来)——一个归档页空、另一个有内容。
如何判断:列 tag、字母序排、扫近重。
grep -rh '^tags:' src/content/articles/ | tr ',' '\n' | sort -u
4. tag 生成器为每个 frontmatter 值都自动建页
tag 生成器走 frontmatter、为每个不同 tag 值建一页、无最小门槛。一次性 tag(单篇文章里的 tag: "expirimental-feature")有一页 tag 页大概会一直空。
如何判断:按 tag 计数文章——1 篇是”瘦 tag”、0 篇是 orphan。
5. 改 tag 名没 redirect
你把 chatgpt 改成 chat-gpt(或反向)——老 tag 页还在但无文章引用——sitemap 还列——404 或 soft-404。
如何判断:URL 路径中存在但当前任何文章 frontmatter 都没用到的 tag。
最短修复路径
Step 1:盘点 orphan tag
跑完整 tag 使用报告:
# scripts/audit-tag-usage.mjs
import fs from "node:fs";
import path from "node:path";
import matter from "gray-matter";
const counts = new Map();
function walk(dir) {
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
const p = path.join(dir, e.name);
if (e.isDirectory()) { walk(p); continue; }
if (!p.endsWith(".mdx")) continue;
const { data } = matter(fs.readFileSync(p, "utf8"));
if (data.draft) continue;
for (const t of (data.tags || [])) {
counts.set(t, (counts.get(t) || 0) + 1);
}
}
}
walk("src/content/articles");
for (const [tag, n] of [...counts.entries()].sort((a, b) => a[1] - b[1])) {
if (n <= 1) console.log(`THIN tag "${tag}": ${n} articles`);
}
0 篇——orphan;1 篇——瘦 tag、大概也不该有 tag 页。
Step 2:按 tag 决定——去索引、合并、回填
每个 orphan/瘦 tag:
- 合并到相似 tag(在文章 frontmatter 中改名、tag 页 redirect)
- 去索引(noindex meta + 从 sitemap 移除)
- 服务器对那个 tag URL 返回 410 Gone
- 有价值——手写 2+ 篇填进去
合并通常最好——折叠近重、集中权威。
Step 3:prebuild 规则——tag 必须 N+ 篇
设最小(如 3 篇已发布)才渲染 archive:
// src/pages/[lang]/tags/[tag].astro
export async function getStaticPaths() {
const all = await getCollection("articles");
const MIN = 3;
const counts = new Map();
for (const a of all) {
if (a.data.draft) continue;
for (const t of (a.data.tags || [])) {
counts.set(`${a.data.lang}:${t}`, (counts.get(`${a.data.lang}:${t}`) || 0) + 1);
}
}
return [...counts.entries()]
.filter(([_, n]) => n >= MIN)
.map(([key]) => {
const [lang, tag] = key.split(":");
return { params: { lang, tag } };
});
}
现在只有 3+ 篇的 tag 有页——空 tag 页不复存在。
Step 4:sitemap 不带去索引/移除的 tag
生 sitemap 的代码也要遵守 MIN 门槛:
// src/pages/sitemap.xml.ts (草图)
const tagsToInclude = /* 同上过滤 */;
// 只为这些发 <url>
之前被索引过的 tag URL——也让服务器返 410 让 Google 尽快移除。Astro 静态产出——在托管层加 redirect/410(Netlify _redirects、Cloudflare Pages _redirects 等):
/en/tags/deprecated-tag/ 410!
/zh/tags/deprecated-tag/ 410!
Step 5:请求重抓 tag 索引页(如有)
如果有 /tags/ 总览页列所有 tag——请求重抓让 Google 看到更新列表。被移除的单个 tag URL——410 在下次抓时触发移除。
预防
- tag 生成器强制每 tag 最少 N 篇(3 合理)
- sitemap 排除门槛以下 tag
- 移除 tag 在托管层返 410
- 改 tag 名必须同 PR 改所有用它的文章
- 季度 tag 审计——合并近重 tag、修剪瘦 tag
- frontmatter tag 值对受控词汇表校验(可选但最干净)