博客 SEO 实战:GSC 索引诊断、i18n 清理与 IndexNow 推送
背景
zcblog 上线之后,GSC 隔三差五报索引问题。顺便把 IndexNow 推送和 GitHub Pages 部署也一并整了。这篇文章就是把排查过程记下来,省得以后忘了。
GSC 索引覆盖诊断
GSC 报的索引问题分了四类:
问题 1:robots.txt 屏蔽页 → 不需要处理
8 个页面被 robots.txt 屏蔽,全是 /redirect/?url=... 跳转中转页。这些页面本就该被屏蔽,外链源头来自 zmki.cn 等外部站点,不影响本站。
robots.txt 配置:
User-agent: *Allow: /Disallow: /redirect/Sitemap: https://marxchou.com/sitemap-index.xml问题 2 页面 → 死链修复
两篇删除的文章仍被 Google 索引,返回 404。处理方式:
- 在
vercel.json或构建配置中添加 301 重定向,指向首页或相关文章 - 或者直接在 GSC 中提交移除请求
问题 3:虚假英文版 URL —— 最坑的一个
Google 索引了大量 /en/article/... 页面,但文章全是中文,Google 觉得这些页面质量不行。
三个层面同时生成了虚假英文版 URL:
@astrojs/sitemapi18n 配置 —— 为所有页面生成了/en/sitemap 条目BaseHead.astro的hreflang标签 —— 告诉搜索引擎 “此页有英文版”- Astro i18n routing —— 自动生成
/en/article/...路由,但内容并未翻译
而实际上 zcblog 没有一篇文章设了 lang: en,26 篇全是中文。
修复步骤:
步骤 1:sitemap 过滤,只包含当前语言
import sitemap from "@astrojs/sitemap";
export default defineConfig({ integrations: [ sitemap({ i18n: { defaultLocale: "zh", locales: { zh: "zh-CN", }, }, filter: (page) => { // 不生成 /en/ 页面的 sitemap return !page.startsWith("https://marxchou.com/en/");4 collapsed lines
}, }), ],});步骤 2:清理 BaseHead 的 hreflang
<!-- 只在确实有翻译时才输出 hreflang -->{ pageLang === "en" && ( <link rel="alternate" hreflang="en" href={enUrl} /> )}步骤 3:英文版页面加 noindex
---// 如果文章没有英文翻译,不索引英文版路由const shouldIndex = frontmatter.lang !== "en" && !Astro.url.pathname.startsWith("/en/");---{!shouldIndex && <meta name="robots" content="noindex" />}问题 4:http:// 旧链接 → 外部引用
部分外部站点引用使用了 http:// 而非 https://。本站已全局强制 HTTPS(Vercel 自动处理),并在 BaseHead 中声明 canonical:
<link rel="canonical" href={new URL(Astro.url.pathname, Astro.site)} />
IndexNow 集成:主动告诉搜索引擎
传统的 SEO 是被动的 —— 更新了网站只能等爬虫路过。IndexNow 反过来了,你更新完直接推送给搜索引擎。
原理
传统方式:网站更新 → 等爬虫路过 → 可能几天后才收录IndexNow:网站更新 → POST 请求通知搜索引擎 → 几乎实时处理支持的搜索引擎有 Bing、Yandex、Seznam。Google 不直接支持 IndexNow,但据说会通过 Bing 的索引间接收到信号。
集成到 Astro 构建流程
在 Astro 集成中挂载构建完成后的钩子,自动提交新 URL:
import type { AstroIntegration } from "astro";
const INDEXNOW_KEY = import.meta.env.INDEXNOW_KEY;const SITE_URL = "https://marxchou.com";
export default function indexnow(): AstroIntegration { return { name: "indexnow", hooks: { "astro:build:done": async ({ pages }) => { const urls = pages .filter((p) => !p.pathname.startsWith("/en/")) .map((p) => `${SITE_URL}${p.pathname}`);
20 collapsed lines
const key = INDEXNOW_KEY; if (!key || urls.length === 0) return;
// 提交给 IndexNow await fetch("https://api.indexnow.org/indexnow", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ host: new URL(SITE_URL).hostname, key, keyLocation: `${SITE_URL}/${key}.txt`, urlList: urls, }), });
console.log(`IndexNow: submitted ${urls.length} URLs`); }, }, };}同时需要在网站根目录放置 key 验证文件 /{key}.txt,内容是 key 本身。

GitHub Pages 双仓部署架构
zcblog 的部署从 Vercel 单仓演进到 GitHub Pages 双仓架构:
架构
你写文章 (Obsidian) ↓ git pushzcblog-articles (私有仓) │ │ push 触发 repository_dispatch │ ▼zcblog (公开仓) │ │ GitHub Actions 构建 │ ▼GitHub Pages (marxchou.com)关键设计
1. 文章仓私有、代码仓公开
文章源码(.md + 图片)放在 zcblog-articles 私有仓,代码放在 zcblog 公开仓。这样:
- 草稿不会提前泄露
- 图片资源不占用公开仓库 LFS 配额
- 公开仓代码可被 fork 学习
2. 构建触发
zcblog-articles 收到 push 后,通过 repository_dispatch 事件通知 zcblog:
- name: Trigger build uses: peter-evans/repository-dispatch@v3 with: token: ${{ secrets.PAT }} repository: smart-chou/zcblog event-type: articles-updatedzcblog 监听该事件自动构建部署:
on: repository_dispatch: types: [articles-updated] push: branches: [main, theme]3. theme 分支独立部署
theme 分支用于主题开发预览,推送后单独构建部署到 theme.marxchou.com,不影响主站。
之前踩的坑
一开始用的是 peaceiris/actions-gh-pages 推到外部仓库 smart-chou.github.io。后来发现 GitHub Pages 内置托管更省事 —— 仓库 Settings → Pages 里选分支和目录就行,不需要额外 action 和外部仓库。

后面注意
- 每季度看一眼 GSC,关注索引覆盖和核心网页指标
- sitemap 别让它脏了,删掉的页面要及时清
- IndexNow key 换了的话记得更新 key 文件
- robots.txt 的 Disallow 定期审一遍,别误伤

