Hugo で 404 ページを作り、Nginx から表示する

Hugo 自宅サーバ 覚書

Hugo サイトで、存在しない URL にアクセスされたときの 404 ページを用意する。

Hugo では layouts/404.html を作ると、ビルド時に public/404.html が生成される。ただし、本番環境で Nginx から配信している場合は、Nginx 側にも「404 のときは /404.html を返す」という設定が必要になる。

今回やることは次の二つ。

  1. Hugo 側で public/404.html を生成する
  2. Nginx 側で 404 時に /404.html を返す

Hugo 側に 404 テンプレートを作る

プロジェクト直下に layouts/404.html を作る。

このサイトでは通常ページと同じレイアウトに載せたいので、main ブロックだけを書いた。

{{ define "title" }}404 Not Found | {{ site.Title }}{{ end }}

{{ define "main" }}
  <article class="article not-found">
    <header class="article-header entry-header">
      <h1 class="entry-title">404 Not Found</h1>
    </header>
    <div class="entry-content cf">
      <p>{{ T "not_found_message" }}</p>
      <p><a class="button-link" href="{{ site.Home.RelPermalink }}">{{ T "back_to_home" }}</a></p>
    </div>
  </article>
{{ end }}

文言は i18n に寄せる。

日本語側の i18n/ja.toml に次を追加する。

[not_found_message]
other = "ページが見つかりませんでした。URLが変わったか、ページが削除された可能性があります。"

[back_to_home]
other = "ホームへ戻る"

英語側の i18n/en.toml にも同じキーを追加する。

[not_found_message]
other = "The page could not be found. The URL may have changed or the page may have been removed."

[back_to_home]
other = "Back to home"

Hugo で生成されるか確認する

テンプレートを追加したら、Hugo をビルドする。

hugo --environment production --minify --cleanDestinationDir

生成結果に public/404.html があるか確認する。

ls -l public/404.html

Windows 側なら PowerShell で確認できる。

Get-Item public\404.html

ここで public/404.html が生成されていれば、Hugo 側の準備は完了。

Nginx で 404.html を返す設定にする

本番環境では、Hugo の公開ディレクトリを Nginx から配信している。

このサイトでは Docker Compose の Nginx コンテナに hugo-public をマウントし、コンテナ内では /var/www/html として見えるようにしている。

その場合、Nginx の server ブロックは次のようにする。

server {
    server_name nosubject.io;

    root /var/www/html;
    index index.html;

    error_page 404 /404.html;

    location = /404.html {
        internal;
    }

    location / {
        try_files $uri $uri/ =404;
    }
}

重要なのはこの行。

error_page 404 /404.html;

try_files でファイルが見つからなかった場合、Nginx は 404 を返す。その 404 に対して error_page を指定しておくと、本文として /404.html が返る。

location / は、Hugo の静的ファイルを配信するための設定。

location / {
    try_files $uri $uri/ =404;
}

location = /404.html { internal; } は、エラー処理用として内部的に使うための指定。なくても動くことはあるが、404 用のファイルとして扱う意図がはっきりするので入れておいた。

Nginx の設定を反映する

Nginx の設定を書き換えたら、まず構文チェックをする。

cd ~/nosubject-docker
docker compose exec web nginx -t

問題なければ reload する。

docker compose exec web nginx -s reload

サービス名が web ではない場合は、先に docker compose ps で確認する。

docker compose ps

表示とステータスコードを確認する

ブラウザで見た目を確認するだけでなく、curl でステータスコードも確認する。

curl -I https://nosubject.io/this-page-does-not-exist/

期待するのは、ステータスコードは 404 のまま、本文は Hugo の 404.html が返る状態。

HTTP/2 404

ローカルの Nginx コンテナに対して確認する場合は、Host ヘッダーを指定する。

curl -k -I https://localhost/this-page-does-not-exist/ -H 'Host: nosubject.io'

ここで 200 OK にしてしまうと、存在しないページが正常ページ扱いになってしまう。検索エンジンにもよくないので、404 ページを表示しつつ、ステータスコードは 404 のままにする。

Hugo server での見え方に注意する

ローカルの hugo server で 404 ページを見たい場合は、直接 /404.html を開いて確認できる。

http://localhost:1313/404.html

ただし、本番の Nginx と同じエラー処理になるわけではない。

本番で重要なのは、Nginx が存在しない URL に対して public/404.html を返すようになっているかどうか。最終確認は本番に近い Nginx 環境で行う。

うまく表示されないときに見るところ

Nginx の既定エラー画面が出る場合は、404.html がないのではなく、Nginx がそのファイルをエラーページとして使っていない可能性がある。

まず、公開ディレクトリに 404.html があるか確認する。

ls -l ~/nosubject-docker/hugo-public/404.html

次に、Nginx の設定に error_page 404 /404.html; が入っているか確認する。

docker compose exec web nginx -T

try_files $uri $uri/ =404; だけでは、Nginx は 404 ステータスを返すだけで、Hugo の 404.html を本文として使ってくれない。

まとめ

Hugo と Nginx の組み合わせで 404 ページを出す手順は次の通り。

  1. layouts/404.html を作る
  2. hugopublic/404.html が生成されることを確認する
  3. 本番の Nginx に error_page 404 /404.html; を追加する
  4. nginx -t で構文チェックする
  5. Nginx を reload する
  6. 存在しない URL へアクセスして、表示と HTTP ステータスを確認する

静的サイトでは、Hugo がファイルを生成するところまでと、Nginx がエラー時にそのファイルを返すところは別の設定になる。

Hugo 側で public/404.html が生成されているのに本番で Nginx のエラー画面が出る場合は、Nginx の error_page 設定を見るのが近道になる。