博客的 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 默认只包含 home 和 page 类型的页面, 如果你有自定义的内容类型 (比如 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 本身无关.