博客的 Google SEO 是怎么做的

这篇文章只讲 Google SEO. 其他搜索引擎不搞, 原因很简单: 流量大头在 Google, 把 Google 做好就够了.

结构化数据

这是最重要的部分. 结构化数据让 Google 在搜索结果里展示 rich result, 包括发布时间, 作者, 摘要等信息.

每篇文章在构建时自动注入 JSON-LD:

{{ if .IsPage }}
<script type="application/ld+json">
{
    "@context": "https://schema.org",
    "@type": "Article",
    "headline": "{{ .Title }}",
    "datePublished": "{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}",
    "dateModified": "{{ .Lastmod.Format "2006-01-02T15:04:05Z07:00" }}",
    "author": {
        "@type": "Person",
        "name": "{{ .Site.Params.author }}"
    },
    "description": "{{ if .Description }}{{ .Description }}{{ else }}{{ .Summary | plainify }}{{ end }}",
    "url": "{{ .Permalink }}"
    {{ with .Params.tags }}
    ,"keywords": "{{ delimit . ", " }}"
    {{ end }}
}
</script>
{{ end }}

关键字段:

  • headline: 文章标题
  • datePublished / dateModified: 发布时间和最后修改时间, 从 Hugo 的 frontmatter 自动获取
  • author: 作者信息, 从站点配置读取
  • description: 优先使用文章专用 description, 没有的话自动截取摘要
  • keywords: 文章标签, 直接作为 keywords 输出
  • url: 文章的 canonical URL

有个容易忽略的事: JSON-LD 里的日期必须是 ISO 8601 格式 (2025-03-17T10:43:00+08:00). 如果你只给了日期没有时区, Google 可能解析不出来. Hugo 的 .Date.Format 默认输出是带时区的, 但如果 frontmatter 里没写时区, 它就默认 UTC, 跟你实际发布时间可能对不上.

这个 script tag 只出现在文章页面 (.IsPage), 首页和列表页不生成.

Meta 标签

<!-- description -->
<meta name='description' itemprop="description"
    content="{{if .IsPage}}{{ truncate 200 .Summary }}{{ else }}{{ .Site.Params.description }}{{ end }}">

<!-- keywords -->
{{ if .Params.keywords }}
<meta name="keywords" content="{{ delimit .Params.keywords ", " }}">
{{else}}
<meta name="keywords" content="{{ delimit .Site.Params.keywords ", " }}">
{{ end }}

<!-- author -->
{{ with .Site.Params.author }}
<meta name="author" content="{{ . }}">{{ end }}

description 限制在 200 字以内. 文章页用内容摘要, 首页用站点描述. keywords 优先用文章自己的关键词, 没有则用站点默认的.

Open Graph

社交媒体分享卡片依赖 Open Graph 协议. 没有这些 meta, 分享链接时只显示一个 URL, 没有标题, 没有描述, 没有缩略图.

<meta property="og:locale" content="{{ if eq .Lang "zh" }}zh_CN{{ else }}en_US{{ end }}">
<meta property="og:title" content="{{ if .Title }}{{ .Title }}{{ else }}{{ .Site.Title }}{{ end }}">
<meta property="og:image"
    content="{{ if .Params.image }}{{ absURL (printf "/images/%s" .Params.image) }}{{ else }}{{ absURL "/images/logo.png" }}{{ end }}">
<meta property="og:description"
    content="{{if .IsPage}}{{ truncate 200 .Summary }}{{ else }}{{ .Site.Params.Description }}{{ end }}">
<meta property="og:site_name" content="{{ .Site.Title }}">

og:image 优先使用文章自定义图片, 没有就用站点 logo. og:locale 根据当前语言自动切换.

Twitter Cards

<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ .Title }}">
<meta name="twitter:description" content="{{ .Description | default .Summary }}">
<meta name="twitter:image" content="{{ .Params.image | absURL }}">

summary_large_image 卡片样式, 在 Twitter 上显示大图预览.

Canonical URL

每页都加上:

<link rel="canonical" href="{{ .Permalink }}">

防止同一内容通过多个 URL 访问时被判定为重复内容.

Alternate hreflang

双语文章通过 hreflang 告诉 Google 哪篇文章对应哪种语言:

{{ if .IsTranslated }}
{{ range .Translations }}
<link rel="alternate" hreflang="{{ .Lang }}" href="{{ .Permalink }}">
{{ end }}
<link rel="alternate" hreflang="{{ .Lang }}" href="{{ .Permalink }}">
{{ end }}

这能避免 Google 把中英文内容判定为重复.

Sitemap

Hugo 自动生成 sitemap.xml, 不需要手动维护. 默认配置每周更新频率, 优先级 0.5.

# config.yml 里
sitemap:
  changefreq: "weekly"
  priority: 0.5

sitemap 提交到 Google Search Console 即可. 不过 Hugo 自动生成的 sitemap 默认只包含 homepage 类型的页面, 如果你有自定义的内容类型 (比如 slides, secret), 需要在 config.yml 里追加 outputs 配置.

搜索引擎验证

static/ 目录下放了各搜索引擎的验证文件:

  • google541e1b3422feb984.html — Google Search Console

Google 的验证最简单, 上传 HTML 文件到站点根目录就行. 不需要改 DNS, 不需要加 meta 标签.

robots.txt

User-agent: *
Allow: /
Sitemap: https://time-friend.com/sitemap.xml

允许所有爬虫, 指向 sitemap.

性能考虑

SEO 相关的工作全是构建时完成的. 结构化数据, meta 标签, sitemap 都是在 Hugo 构建时生成的静态内容. 对页面加载性能完全没有影响.

JSON-LD script tag 大约 500 字节, 可以忽略不计.

唯一需要运行时的部分是 Google Analytics, 加载的是外部的 analytics.js. 但这是为了获取访问数据, 跟 SEO 本身无关.

文章链接:

/zh/archive/blog-architecture-google-seo/

# 相关文章推荐