What I Did for Google SEO on This Blog
This article only covers Google SEO. No other search engines. Reason: Google has the traffic share, doing Google well is enough.
Structured Data
The most important part. Structured data lets Google show rich results in search — publish date, author, summary.
Every article gets JSON-LD injected at build time:
{{ 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 }}
One thing to note: dates in JSON-LD must be ISO 8601 format (2025-03-17T10:43:00+08:00). If you only provide date without timezone, Google might not parse it correctly. Hugo’s .Date.Format outputs timezone by default, but if frontmatter doesn’t have timezone info, it assumes UTC — might not match your actual publish time.
Key fields:
- headline: article title
- datePublished / dateModified: from Hugo frontmatter
- author: from site config
- description: article description or auto-truncated summary
- keywords: article tags
- url: canonical URL
Meta Tags
<meta name='description' itemprop="description"
content="{{if .IsPage}}{{ truncate 200 .Summary }}{{ else }}{{ .Site.Params.description }}{{ end }}">
{{ if .Params.keywords }}
<meta name="keywords" content="{{ delimit .Params.keywords ", " }}">
{{else}}
<meta name="keywords" content="{{ delimit .Site.Params.keywords ", " }}">
{{ end }}
{{ with .Site.Params.author }}
<meta name="author" content="{{ . }}">{{ end }}
Description limited to 200 characters. Article pages use content summary, homepage uses site description.
Open Graph
Social media share cards depend on Open Graph. Without these meta tags, sharing an article shows only a URL — no title, no description, no thumbnail.
<meta property="og:locale" content="{{ if eq .Lang "zh" }}zh_CN{{ else }}en_US{{ end }}">
<meta property="og:title" content="{{ .Title }}">
<meta property="og:image" content="{{ .Params.image | absURL }}">
<meta property="og:description" content="{{ .Summary | truncate 200 }}">
<meta property="og:site_name" content="{{ .Site.Title }}">
og:image uses article-specific image if available, falls back to site logo.
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 card style for big image preview on Twitter.
Canonical URL
<link rel="canonical" href="{{ .Permalink }}">
Prevents duplicate content issues when the same article is accessible via multiple URLs.
Alternate hreflang
Tells Google which article corresponds to which language:
{{ if .IsTranslated }}
{{ range .Translations }}
<link rel="alternate" hreflang="{{ .Lang }}" href="{{ .Permalink }}">
{{ end }}
<link rel="alternate" hreflang="{{ .Lang }}" href="{{ .Permalink }}">
{{ end }}
Prevents Google from flagging Chinese and English content as duplicates.
Sitemap
Hugo generates sitemap.xml automatically. Default config: weekly update frequency, priority 0.5.
sitemap:
changefreq: "weekly"
priority: 0.5
Submit to Google Search Console. One thing: Hugo’s default sitemap only includes home and page content types. If you have custom content types (like slides, secret), you need to add them in config.yml outputs.
Search Engine Verification
Verification file in static/:
google541e1b3422feb984.html— Google Search Console
Google’s verification is the simplest: upload an HTML file to the site root. No DNS changes needed.
robots.txt
User-agent: *
Allow: /
Sitemap: https://time-friend.com/sitemap.xml
Allow all crawlers, point to sitemap.
Performance
All SEO work happens at build time. Structured data, meta tags, sitemap are all static content generated during Hugo build. Zero impact on page load performance.
JSON-LD script tag is about 500 bytes, negligible.