跳转到主要内容

分析与 SEO

接入一个分析服务(或者不接),并把主题已经生成的 canonical、hreflang、社交卡片、站点地图与 robots 配对。

主题默认不加载任何分析、表单或广告脚本,不配置就没有对外请求。接入需要显式配置,并把这条外部数据边界写进站点的隐私说明。SEO 一侧相反:canonical、hreflang、robots meta、Open Graph 与 Twitter 卡片由主题逐页生成,需要你做的是把 baseURL 与每页的 description 写对。

接 Google Analytics

用 Hugo 内置的服务配置。启用前,将 G-YOUR_MEASUREMENT_ID 换成你自己的 GA4 measurement ID:

hugo.yml
services:
  googleAnalytics:
    id: G-YOUR_MEASUREMENT_ID

主题只在 production 环境渲染这段脚本。普通 hugo server 默认使用 development, 不会上报;但 hugo 构建默认 production,即使运行在预览宿主上也一样。PR 与 staging 部署需要明确选择非 production 环境,并将 PREVIEW_URL 设为预览的实际地址:

终端
hugo --panicOnWarning --environment staging --baseURL "$PREVIEW_URL"

详见预览部署配置。

不要同时设置已经弃用的顶层 googleAnalytics 键。不需要分析时删掉整段配置,不要填一个假 ID。

这与网络隔离环境不兼容

配上之后,页面浏览量与事件会发给 Google。严格的同源内容安全策略也需要为它放行,见内容安全策略。这是站点决策,不是主题默认。

接其它分析服务

Plausible、Umami、Matomo 这类服务只要求插入一段脚本。主题提供两个注入点,在站点仓库里建同名文件即可,不用改主题:

layouts/_partials/hooks/head-end.html , 插入位置</head> 之前,在 Google Analytics 模板之前
分析脚本、cookie 同意脚本、主题没提供的 meta 标签
layouts/_partials/hooks/body-end.html , 插入位置页面脚本的最后
只影响交互、不影响首屏的第三方代码

使用 Plausible 时,先把 your-site.example 换成你自己账户中登记的域名,再加入这个钩子:

layouts/_partials/hooks/head-end.html
{{ if hugo.IsProduction }}
<script defer data-domain="your-site.example"
        src="https://plausible.io/js/script.js"></script>
{{ end }}

hugo.IsProduction 这一层不要省:没有它,每个人的本地预览都会向你的统计上报数据。

head-end 在 Google Analytics 之前执行

这是有意的:cookie 同意脚本必须先于分析脚本运行,才能真正拦住它。

「这篇文档解决了你的问题吗」反馈组件是另一件事:默认关闭,不发网络请求,配置见仓库与页面信息。

页面描述

<meta name="description"> 按这个顺序取值,取到第一个非空的就停:

  1. 页面 front matter 的 description
  2. Hugo 计算出的页面摘要(.Summary)
  3. 站点配置里的 params.description

每页写一句 description 是唯一需要作者做的 SEO 动作。它同时用于三处:搜索引擎的摘要、栏目首页的卡片副标题、站内搜索的结果预览。

content/docs/admin/analytics.zh.md(本页)
---
title: 分析与 SEO
description: 接入一个分析服务(或者不接),并把主题已经生成的 canonical、hreflang、社交卡片、站点地图与 robots 配对。
---

多语言站点要给每种语言各写一句,不要把英文描述抄到中文页上。站点级默认值也是分语言的:

hugo.yml
languages:
  en:
    params:
      description: A Hugo theme for engineering docs
  zh:
    params:
      description: 为工程而设计的 Hugo 文档主题

canonical 与 hreflang

主题为每个页面输出一条 canonical,并为实际译文输出 hreflang 备用链接,不需要配置:

渲染结果(本页)
<link rel="canonical" href="https://oink.pgsty.com/zh/docs/admin/analytics/">
<link rel="alternate" hreflang="zh-CN" href="https://oink.pgsty.com/zh/docs/admin/analytics/">
<link rel="alternate" hreflang="en-US" href="https://oink.pgsty.com/docs/admin/analytics/">

hreflang 的语言代码来自各语言的 locale(本站是 en-US / zh-CN),链接来自 Hugo 的译文关系。1.2.0 实现会从 hreflang 和 og:locale:alternate 中省略 缺失的译文。可见的语言切换器仍可跳到目标语言首页,但这种导航回退不代表译文关系。

博客索引的每一分页使用自身的 canonical URL。从第 2 页起不输出语言备用链接, 因为分页不代表各语言存在一一对应的译文页。这些修正已随 1.2.0 发布; 1.1.0 仍保留之前的行为。

canonical 由 baseURL 拼出。baseURL 配错时 canonical 会把搜索引擎指向不存在的地址,比构建失败更难发现。上线前照发布上线的验收清单查一遍。

多语言的完整配置在多语言。

社交卡片

主题调用 Hugo 内置的 Open Graph 与 Twitter 卡片模板,标题、描述、URL、语言、站名都是自动的:

渲染结果(本页)
<meta property="og:title" content="分析与 SEO">
<meta property="og:type" content="article">
<meta property="og:url" content="https://oink.pgsty.com/zh/docs/admin/analytics/">
<meta property="og:locale" content="zh_CN">
<meta property="og:locale:alternate" content="en_US">
<meta name="twitter:card" content="summary">

要让分享出去的链接带图,在 front matter 里给 images:

任意页面
---
title: OINK v0.8.0 发布
images: [/images/releasenote.webp]
---

给全站一张兜底图就把同样的键写进 params:

hugo.yml
params:
  images: [/images/oink.webp]

有图时 twitter:card 从 summary 变成 summary_large_image,并多出 og:image 与 twitter:image 两条。本站两处都没有设置,上面的渲染结果里因此看不到图片相关的标签。

站点地图

Hugo 自动生成,多语言站点生成的是一个索引:

public/ 下的结构
sitemap.xml        ← 索引,指向下面两个
en/sitemap.xml
zh/sitemap.xml

站点级默认值和页面级覆盖都是 Hugo 原生的:

hugo.yml
sitemap:
  changefreq: monthly
  filename: sitemap.xml
  priority: 0.5
某个页面
---
title: 发布说明
sitemap:
  priority: 0.8
---

changefreq 与 priority 是提示不是承诺,搜索引擎可以忽略。值得做的是发布前确认草稿、私有内容与非规范副本没有进入站点地图,并且每种语言的那份都生成了。

robots.txt 与不收录

Hugo 只在站点配置里打开开关时才生成 robots.txt:

hugo.yml
enableRobotsTXT: true

主题提供的模板按构建环境给出两种结果,不需要你写内容:

production 构建
User-agent: *
Allow: /

Sitemap: https://oink.pgsty.com/sitemap.xml
非 production 构建
User-agent: *
Disallow: /

页面里的 robots meta 跟着同一个开关走:production 且不是打印输出时是 index, follow,否则是 noindex, nofollow。预览部署不要用 --environment production 构建,非 production 自带不收录的行为。

主题没有按页 noindex 的开关。某一页不该被收录时,可靠的做法是不发布它(draft: true,或用 Hugo 的 _build 选项)。既要发布又不想被收录,就用 head-end.html 钩子自己输出;主题已经输出了一条 robots meta,两条同时存在时如何合并由搜索引擎决定。

收录检查

上线一两周后,按这个顺序确认搜索引擎看到的东西和你以为的一致:

  1. 抓取权限:访问 <baseURL>/robots.txt,确认是 Allow: / 而不是 Disallow: /。
  2. 页面清单:访问 <baseURL>/sitemap.xml,点进语言子地图,看页面数量对不对。
  3. 收录数量:在搜索引擎里查 site:你的域名,数量级对得上就行,不必逐页核对。
  4. 规范地址:搜索结果应当落在 canonical 指向的 URL 上,而不是带 ? 参数或旧域名的版本。
  5. 主动提交:在 Google Search Console / Bing Webmaster Tools 里加上站点并提交 sitemap.xml 的地址,比等着被爬快。

搜索元数据补不了内容本身的问题:单薄、重复、过时的页面,写再好的 description 也一样。

验证

在自己的站点根目录执行。将 PAGE 换成自己站点实际生成的页面,并按需包含语言前缀:

终端
hugo --gc --minify --printPathWarnings --panicOnWarning
PAGE=public/zh/docs/getting-started/index.html
test -f "$PAGE"

# canonical 应使用真实生产地址;production 的 robots 允许收录。
grep -o '<link[^>]*canonical[^>]*>' "$PAGE"
grep -o '<meta[^>]*robots[^>]*>' "$PAGE"
cat public/robots.txt
head -5 public/sitemap.xml

# 未配置 Google Analytics 时,应没有匹配结果。
grep -o '<script[^>]*googletagmanager[^>]*>' "$PAGE"

在浏览器 Network 面板确认已配置的统计请求使用自己的 measurement ID 或登记域名。 再检查用 --environment staging 构建的预览部署,应没有统计请求。未配置分析时, 两种环境均不应产生统计请求;其他显式启用的集成仍可能访问各自的远程服务。

  • 发布上线 — baseURL、验收清单与预览部署不被收录
  • 仓库与页面信息 — 页面反馈组件、编辑本页与最后修改时间
  • 多语言 — 语言配置决定 hreflang 与译文关系
  • Agent 支持 — 给大模型看的 .md 输出与 llms.txt
  • 配置总览 — services、sitemap、enableRobotsTXT 等键的定义