编程 GSC 反复报索引问题:清理虚假 /en/ 页面、接入 IndexNow 与 GitHub Pages 双仓部署

2026-09-18 00:04:14

GSC 反复报索引问题:清理虚假 /en/ 页面、接入 IndexNow 与 GitHub Pages 双仓部署

相关地址:站点 marxchou.com | 代码仓 smart-chou/zcblog

背景

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:404 页面 → 死链修复

两篇删除的文章仍被 Google 索引,返回 404。处理方式:

  • vercel.json 或构建配置中添加 301 重定向,指向首页或相关文章
  • 或者直接在 GSC 中提交移除请求

问题 3:虚假英文版 URL

Google 索引了大量 /en/article/... 页面,但文章全是中文,Google 觉得这些页面质量不行。

三个层面同时生成了虚假英文版 URL:

  1. @astrojs/sitemap i18n 配置 —— 为所有页面生成了 /en/ sitemap 条目
  2. BaseHead.astro 的 hreflang 标签 —— 告诉搜索引擎“此页有英文版”
  3. Astro i18n routing —— 自动生成 /en/article/... 路由,但内容并未翻译

而实际上 zcblog 没有一篇文章设了 lang: en,26 篇全是中文。

修复步骤:

步骤 1:sitemap 过滤,只包含当前语言

astro.config.mjs

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/");
},
}),
],
});

步骤 2:清理 BaseHead 的 hreflang

{ pageLang === "en" && (

)}

步骤 3:英文版页面加 noindex

const shouldIndex = frontmatter.lang !== "en" && !Astro.url.pathname.startsWith("/en/");
{!shouldIndex && }

问题 4:http:// 旧链接 → 外部引用

部分外部站点引用使用了 http:// 而非 https://。本站已全局强制 HTTPS(Vercel 自动处理),并在 BaseHead 中声明 canonical:


IndexNow 集成:主动告诉搜索引擎

传统方式是更新网站后等爬虫路过。IndexNow 是在更新完成后直接推送给搜索引擎。

原理

传统方式:网站更新 → 等爬虫路过 → 可能几天后才收录

IndexNow:网站更新 → POST 请求通知搜索引擎 → 几乎实时处理

支持的搜索引擎有 Bing、Yandex、Seznam。Google 不直接支持 IndexNow,但据说会通过 Bing 的索引间接收到信号。使用前提是配置 INDEXNOW_KEY,并在网站根目录放置 /{key}.txt 验证文件。

集成到 Astro 构建流程

在 Astro 集成中挂载构建完成后的钩子,自动提交新 URL:

src/integrations/indexnow.ts

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}`);
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 push → zcblog-articles (私有仓) → push 触发 repository_dispatch → zcblog (公开仓) → GitHub Actions 构建 → GitHub Pages (marxchou.com)

关键设计

  1. 文章仓私有、代码仓公开

文章源码(.md + 图片)放在 zcblog-articles 私有仓,代码放在 zcblog 公开仓。这样:草稿不会提前泄露;图片资源不占用公开仓库 LFS 配额;公开仓代码可被 fork 学习。

  1. 构建触发

zcblog-articles 收到 push 后,通过 repository_dispatch 事件通知 zcblog:

zcblog-articles/.github/workflows/notify.yml

- name: Trigger build
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.PAT }}
repository: smart-chou/zcblog
event-type: articles-updated

zcblog 监听该事件自动构建部署:

zcblog/.github/workflows/deploy.yml

on:
repository_dispatch:
types: [articles-updated]
push:
branches: [main, theme]
  1. 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 定期审一遍,别误伤

推荐文章

程序员茄子在线接单