Hugoで構築したサイトは表示速度に優れる一方、SEOに必要なメタ情報や構造化データを自前で設定しなければなりません。静的サイトジェネレーター(SSG)はデータベースを持たないため、WordPressのようにプラグインで自動補完する仕組みがなく、設定ファイルやテンプレートへ直接記述する必要があります。

本記事では、Hugoプロジェクトのhugo.tomlから各テンプレートファイルまで、コピーして即使える設定コードとともにSEO施策を体系的にまとめています。

Hugoが検索エンジンに強い理由と弱点

静的HTMLの速度優位性

HugoはGoで実装されたSSGで、数千ページ規模のサイトでもミリ秒単位でビルドが完了します。生成物は純粋なHTMLファイルのため、サーバーサイド処理が不要でTTFB(Time To First Byte)が極めて短くなります。GoogleはCore Web Vitalsをランキングシグナルの一つとしており、この速度面はSEOにとって大きなアドバンテージです。

SSG特有のSEO課題

一方で、以下の点は手動設定が必須です。

課題WordPressとの違いHugoでの対応方法
メタタグ管理プラグインが自動生成テンプレートで<meta>を出力
サイトマッププラグインで生成hugo.tomlで有効化
構造化データプラグインで挿入JSON-LDテンプレートを作成
リダイレクト管理.htaccessやプラグインaliasesやデプロイ先の設定
動的コンテンツPHPで処理API連携 or ビルド時に生成

hugo.tomlで設定すべきSEO基本項目

サイト全体のSEO基盤はhugo.toml(旧config.toml)で定義します。以下は主要な設定項目です。

baseURL = "https://example.com/"
languageCode = "ja"
defaultContentLanguage = "ja"
title = "サイトタイトル"

# URL末尾のスラッシュを統一(正規化)
[permalinks]
  posts = "/:section/:slug/"

# サイトマップ自動生成
[sitemap]
  changefreq = "weekly"
  filename = "sitemap.xml"
  priority = 0.5

# robots.txtをテンプレートから生成
enableRobotsTXT = true

# RSSフィード生成
[outputs]
  home = ["HTML", "RSS", "JSON"]
  section = ["HTML", "RSS"]

# タクソノミー(カテゴリ・タグ)
[taxonomies]
  category = "categories"
  tag = "tags"

# 関連記事の自動検出
[related]
  includeNewer = true
  threshold = 80
  toLower = true

  [[related.indices]]
    name = "tags"
    weight = 100

  [[related.indices]]
    name = "categories"
    weight = 80

  [[related.indices]]
    name = "date"
    weight = 10

パーマリンク設計のポイント

URL構造は検索エンジンがページの内容を推測する手がかりになります。日本語URLはエンコードされると非常に長くなるため、slugフィールドに英語の短い文字列を指定する運用がおすすめです。

---
title: "HugoサイトのSEO対策"
slug: "hugo-seo-guide"
---

この設定により https://example.com/posts/hugo-seo-guide/ のようなクリーンなURLが生成されます。

title・descriptionタグの最適な出力方法

headテンプレートの実装

layouts/partials/head.htmlにメタタグを集約します。

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  {{/* ページタイトル */}}
  <title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} | {{ .Site.Title }}{{ end }}</title>

  {{/* メタディスクリプション */}}
  {{ with .Description }}
    <meta name="description" content="{{ . }}">
  {{ else }}
    {{ with .Summary }}
      <meta name="description" content="{{ . | plainify | truncate 160 }}">
    {{ end }}
  {{ end }}

  {{/* Canonical URL */}}
  <link rel="canonical" href="{{ .Permalink }}">

  {{/* ページ固有のキーワード */}}
  {{ with .Params.tags }}
    <meta name="keywords" content="{{ delimit . ", " }}">
  {{ end }}
</head>

Front Matterでのdescription指定

各記事のFront Matterにdescriptionを明示的に記述しておくと、検索結果のスニペットに反映されやすくなります。省略した場合は.Summary(記事冒頭の自動抽出)がフォールバックとして使われますが、検索意図に合ったクリック率の高い文面を手動で書く方が効果的です。

---
title: "記事タイトル"
description: "120〜160文字程度で、検索ユーザーの課題と記事で得られる情報を端的にまとめる"
---

OGP(Open Graph Protocol)とTwitter Cardの設定

SNSでシェアされた際の表示を制御するOGPタグは、クリック率とトラフィックに直結します。

OGPテンプレート

{{/* OGP */}}
<meta property="og:title" content="{{ .Title }}">
<meta property="og:description" content="{{ with .Description }}{{ . }}{{ else }}{{ .Summary | plainify | truncate 200 }}{{ end }}">
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}">
<meta property="og:url" content="{{ .Permalink }}">
{{ with .Params.image }}
  <meta property="og:image" content="{{ . | absURL }}">
{{ else }}
  <meta property="og:image" content="{{ "images/default-ogp.png" | absURL }}">
{{ end }}
<meta property="og:site_name" content="{{ .Site.Title }}">
<meta property="og:locale" content="ja_JP">

{{/* Twitter Card */}}
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ .Title }}">
<meta name="twitter:description" content="{{ with .Description }}{{ . }}{{ else }}{{ .Summary | plainify | truncate 200 }}{{ end }}">
{{ with .Params.image }}
  <meta name="twitter:image" content="{{ . | absURL }}">
{{ end }}

og:imageにはFront Matterのimageパラメータを使い、未指定時はデフォルト画像にフォールバックさせます。推奨サイズは1200x630pxです。

構造化データ(JSON-LD)をテンプレートで自動出力する

構造化データを設定すると、検索結果にリッチスニペット(FAQ、パンくずリスト、記事情報など)が表示される可能性が高まります。

記事ページ用のArticleスキーマ

layouts/partials/structured-data.htmlを作成します。

{{ if .IsPage }}
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "{{ .Title }}",
  "description": "{{ with .Description }}{{ . }}{{ else }}{{ .Summary | plainify | truncate 200 }}{{ end }}",
  "url": "{{ .Permalink }}",
  "datePublished": "{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}",
  "dateModified": "{{ .Lastmod.Format "2006-01-02T15:04:05Z07:00" }}",
  {{ with .Params.image }}
  "image": "{{ . | absURL }}",
  {{ end }}
  "author": {
    "@type": "Person",
    "name": "{{ .Site.Params.author | default .Site.Title }}"
  },
  "publisher": {
    "@type": "Organization",
    "name": "{{ .Site.Title }}",
    "logo": {
      "@type": "ImageObject",
      "url": "{{ "images/logo.png" | absURL }}"
    }
  }
}
</script>
{{ end }}

パンくずリストのBreadcrumbスキーマ

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "ホーム",
      "item": "{{ .Site.BaseURL }}"
    }
    {{ if .CurrentSection }}
    ,{
      "@type": "ListItem",
      "position": 2,
      "name": "{{ .CurrentSection.Title }}",
      "item": "{{ .CurrentSection.Permalink }}"
    }
    {{ end }}
    {{ if .IsPage }}
    ,{
      "@type": "ListItem",
      "position": 3,
      "name": "{{ .Title }}",
      "item": "{{ .Permalink }}"
    }
    {{ end }}
  ]
}
</script>

これらのパーシャルをbaseof.html</body>直前で呼び出せば、全ページに構造化データが自動挿入されます。

サイトマップとrobots.txtの設定

サイトマップ

Hugo はhugo.toml[sitemap]を設定するだけでビルド時にXMLサイトマップを自動生成します。生成されたサイトマップはGoogle Search Consoleに登録して、インデックス状況を監視します。

カスタムテンプレートが必要な場合はlayouts/_default/sitemap.xmlを作成します。

{{ printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  {{ range .Data.Pages }}
  {{ if not .Params.noindex }}
  <url>
    <loc>{{ .Permalink }}</loc>
    {{ if not .Lastmod.IsZero }}
    <lastmod>{{ .Lastmod.Format "2006-01-02T15:04:05Z07:00" }}</lastmod>
    {{ end }}
    <changefreq>{{ with .Sitemap.ChangeFreq }}{{ . }}{{ else }}weekly{{ end }}</changefreq>
    <priority>{{ with .Sitemap.Priority }}{{ . }}{{ else }}0.5{{ end }}</priority>
  </url>
  {{ end }}
  {{ end }}
</urlset>

noindex: trueを指定したページをサイトマップから除外するロジックを入れている点がポイントです。

robots.txt

hugo.tomlenableRobotsTXT = trueを設定し、layouts/robots.txtを作成します。

User-agent: *
Allow: /

Sitemap: {{ .Site.BaseURL }}sitemap.xml

ステージング環境や下書きページをクロール対象外にしたい場合は、Disallowディレクティブを追加します。

Hugo Pipesによるアセット最適化

Hugo Pipesを使うと、CSS・JavaScriptの処理をビルドパイプラインに組み込めます。外部ツールを使わずにアセットを最適化できるため、ビルドの複雑性を抑えつつCore Web Vitalsのスコアを改善できます。

CSSの圧縮とフィンガープリント付与

{{ $style := resources.Get "css/main.css" }}
{{ $style = $style | minify | fingerprint }}
<link rel="stylesheet" href="{{ $style.Permalink }}" integrity="{{ $style.Data.Integrity }}">

SCSSのコンパイル + 圧縮

{{ $scss := resources.Get "scss/main.scss" }}
{{ $style := $scss | toCSS | minify | fingerprint }}
<link rel="stylesheet" href="{{ $style.Permalink }}" integrity="{{ $style.Data.Integrity }}">

JavaScriptのバンドルと圧縮

{{ $js := resources.Get "js/main.js" | js.Build | minify | fingerprint }}
<script src="{{ $js.Permalink }}" integrity="{{ $js.Data.Integrity }}" defer></script>

defer属性を付けることでHTMLパースをブロックしないため、LCP(Largest Contentful Paint)の改善に寄与します。

画像の最適化手法

画像はページ容量の大部分を占めるケースが多く、SEOにおけるCore Web Vitals改善で最もインパクトのある施策です。

Hugo組み込みの画像リサイズ

{{ $image := resources.Get "images/hero.jpg" }}
{{ $resized := $image.Resize "800x webp" }}
<img src="{{ $resized.Permalink }}"
     width="{{ $resized.Width }}"
     height="{{ $resized.Height }}"
     alt="説明テキスト"
     loading="lazy">

Hugo 0.83以降(Extended版)ではWebP変換にネイティブ対応しています。widthheight属性を明示することでCLS(Cumulative Layout Shift)を防げます。

レスポンシブ画像の出力

{{ $img := resources.Get "images/hero.jpg" }}
{{ $small := $img.Resize "480x webp" }}
{{ $medium := $img.Resize "800x webp" }}
{{ $large := $img.Resize "1200x webp" }}

<img srcset="{{ $small.Permalink }} 480w,
             {{ $medium.Permalink }} 800w,
             {{ $large.Permalink }} 1200w"
     sizes="(max-width: 600px) 480px,
            (max-width: 1024px) 800px,
            1200px"
     src="{{ $medium.Permalink }}"
     alt="説明テキスト"
     loading="lazy"
     width="{{ $medium.Width }}"
     height="{{ $medium.Height }}">

端末の画面幅に応じて最適なサイズの画像を配信でき、モバイルユーザーの通信量削減と表示速度向上の両方を実現できます。

内部リンクと関連記事の実装

内部リンクはクローラーがサイト構造を把握するための重要な手がかりであり、ユーザーの回遊率向上にもつながります。

関連記事テンプレート

hugo.toml[related]を設定済みの前提で、記事下部に関連記事を表示するテンプレートを作成します。

{{ $related := .Site.RegularPages.Related . | first 5 }}
{{ with $related }}
<aside>
  <h2>関連する記事</h2>
  <ul>
    {{ range . }}
    <li>
      <a href="{{ .Permalink }}">{{ .Title }}</a>
      <time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "2006年1月2日" }}</time>
    </li>
    {{ end }}
  </ul>
</aside>
{{ end }}

パンくずリストのHTML出力

<nav aria-label="パンくずリスト">
  <ol>
    <li><a href="{{ .Site.BaseURL }}">ホーム</a></li>
    {{ if .CurrentSection }}
    <li><a href="{{ .CurrentSection.Permalink }}">{{ .CurrentSection.Title }}</a></li>
    {{ end }}
    <li aria-current="page">{{ .Title }}</li>
  </ol>
</nav>

aria-labelaria-currentを付与することでアクセシビリティも確保できます。

デプロイ先ごとのSEO関連設定

Hugoサイトのデプロイ先によって、リダイレクトやヘッダー設定の方法が異なります。

設定項目GitHub PagesNetlifyCloudflare PagesVercel
HTTPS強制リポジトリ設定で有効化自動自動自動
リダイレクト設定Hugoのaliases_redirectsファイル_redirectsファイルvercel.json
カスタムヘッダー不可_headersファイル_headersファイルvercel.json
キャッシュ制御限定的_headersで自在_headersで自在vercel.json
ビルドコマンドGitHub Actionshugo --minifyhugo --minifyhugo --minify

Netlifyでのヘッダー設定例

static/_headersを作成します。

/*
  X-Frame-Options: DENY
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin

/css/*
  Cache-Control: public, max-age=31536000, immutable

/js/*
  Cache-Control: public, max-age=31536000, immutable

/images/*
  Cache-Control: public, max-age=2592000

フィンガープリント付きのCSS・JSには長期キャッシュを設定し、画像には30日間のキャッシュを適用することで、リピーターのページ読み込み速度が大幅に改善されます。

Google Search Consoleとの連携手順

サイトを公開したら、Google Search Consoleに登録してインデックス状況を把握します。

所有権確認の方法

  1. Google Search Console にアクセスし「プロパティを追加」を選択
  2. URLプレフィックスにサイトのURLを入力
  3. HTMLタグによる確認を選択し、表示されたメタタグをlayouts/partials/head.htmlに追加
<meta name="google-site-verification" content="ここに確認コード">
  1. サイトをビルド・デプロイしてから「確認」ボタンを押す
  2. 確認完了後、サイトマップURL(https://example.com/sitemap.xml)を登録

インデックス登録の促進

新規記事を公開したら、Search Consoleの「URL検査」から該当URLを入力し「インデックス登録をリクエスト」を実行すると、クロールの優先度が上がります。

Core Web Vitalsを改善するHugo固有の施策

GoogleのCore Web VitalsはLCP(最大コンテンツの表示時間)、INP(入力への応答速度)、CLS(レイアウトのずれ)の3指標で構成されます。

LCPの改善

  • Hugo Pipesで CSS/JSを圧縮し、配信サイズを削減する
  • ファーストビューの画像にはloading="lazy"を付けず、fetchpriority="high"を指定する
  • フォントの読み込みにfont-display: swapを使い、テキスト描画をブロックしない
{{ $heroImage := resources.Get "images/hero.jpg" }}
{{ $hero := $heroImage.Resize "1200x webp" }}
<img src="{{ $hero.Permalink }}"
     width="{{ $hero.Width }}"
     height="{{ $hero.Height }}"
     alt="メインビジュアル"
     fetchpriority="high">

CLSの改善

  • 画像にwidthheightを必ず指定する(Hugo の.Width.Heightで自動取得可能)
  • 広告やウィジェットの挿入領域にmin-heightを確保する
  • Web Fontの読み込みによるレイアウトシフトをfont-display: optionalで軽減する

INPの改善

Hugoが生成する静的HTMLはサーバーサイド処理がないため、そもそもINPが悪化しにくい構造です。JavaScriptによるインタラクションを最小限に抑え、defer属性で非同期読み込みすることが基本方針になります。

SEO対策チェックリスト

Hugoサイトを公開する前に確認すべき項目を一覧にまとめます。

設定ファイル

  • baseURLが本番ドメインになっている
  • enableRobotsTXT = trueが設定されている
  • [sitemap]セクションが定義されている
  • [permalinks]でクリーンURLを設定している
  • [related]で関連記事の検出ルールを定義している

テンプレート

  • <title>タグがページごとに一意になっている
  • <meta name="description">が出力されている
  • Canonical URLが全ページに設定されている
  • OGPタグ(og:title, og:description, og:image)が出力されている
  • 構造化データ(JSON-LD)が記事ページに含まれている
  • パンくずリストが表示されている

パフォーマンス

  • CSS/JSがminifyされている
  • 画像がWebP形式に変換されている
  • loading="lazy"が非ファーストビュー画像に設定されている
  • widthheight属性が全画像に付与されている

インデックス管理

  • Google Search Consoleに所有権確認が完了している
  • サイトマップURLが登録されている
  • 公開したくないページにnoindexを設定している

このチェックリストを全て満たせば、Hugoサイトの技術的なSEO基盤は整った状態です。あとはユーザーの検索意図に合った質の高いコンテンツを定期的に公開していくことで、検索順位の向上が期待できます。