朝に書いた記事をpushして、GitHub Actionsのビルドも緑色で終わったのに、サイトのどこにも記事が出てこない。ビルドのログを見てもエラーも警告もなく、記事のファイルはリポジトリにきちんと入っている。このブログも、立ち上げてすぐのころに一度これを踏んだ。原因は、Hugoが日付を読むときのタイムゾーンと、未来の日付の記事を除外するbuildFutureの組み合わせにあった。

同じところで二度迷わないように、仕組みと直し方をまとめておく。手元のWindows 11に入っているHugo v0.166.0(GitHub Actions側も同じ版)で、最小構成のサイトを作って症状を再現し、どの書き方なら出てどの書き方なら消えるのかを確かめた。結論を先に書くと、設定ファイルにtimeZone = 'Asia/Tokyo'を1行足すか、記事の日付に+09:00を付ければ直る。ただし、どちらも知らないまま運用すると、日本の朝の時間帯にだけ起きる分かりにくい不具合になる。

Hugoは時刻のない日付を、UTCの0時として読む

ロンドン・グリニッジ天文台の門の脇にある、24時間表示の白い文字盤の時計
グリニッジ天文台の門の脇にある24時間表示の時計。Hugoが既定で使うUTCは、この天文台を通る子午線の時刻(グリニッジ標準時)の後継にあたるPhoto: Alvesgaspar / CC BY-SA 3.0

記事のfront matterにdate: 2026-10-16と書いたとき、そこには時刻もタイムゾーンも入っていない。人間はそれを「日本の10月16日」と読むが、Hugoには日本で書かれたことを知る手段がない。そこでHugoは、設定ファイルのtimeZoneを見て、その地域の時刻として解釈する。そしてtimeZoneを書いていないときの既定はUTC(協定世界時)になっている。

つまり2026-10-16は、何も設定していなければ「UTCの10月16日0時」、日本時間に直すと10月16日の午前9時として扱われる。date: 2026-10-16T07:00:00のように時刻まで書いても、オフセットが無ければ同じで、UTCの7時、日本時間の16時と読まれる。書いた本人の感覚より、ちょうど9時間うしろにずれる計算だ。

もうひとつ見落としやすいのが、Hugoを動かしているパソコンのタイムゾーンは関係しないという点だ。手元のパソコンは日本時間で動いているが、それでもtimeZoneが無ければUTCとして読まれた。環境変数のTZにAsia/Tokyoを入れて試しても、日付の解釈は変わらなかった。日付をどう読むかは、Hugoの設定だけで決まる。

buildFutureの既定はfalse。未来の記事は何も言わずに外される

Hugoには、公開日が未来の記事をビルドに含めない仕組みがある。予約投稿のような使い方を想定したもので、設定のbuildFutureが既定でfalseになっている。下書きを外すbuildDrafts、有効期限が過ぎた記事を外すbuildExpiredも同じく既定はfalseだ。

ここで先ほどのずれが効いてくる。日本時間の10月16日の朝7時30分は、UTCではまだ10月15日の22時30分だ。この時点でdate: 2026-10-16の記事をビルドすると、Hugoから見れば「UTCの16日0時」はまだ1時間半先の未来なので、記事はビルドから外される。日付だけの記事なら、日本時間の0時から9時までの9時間、当日の記事が必ず未来扱いになる。

困るのは、このとき何の知らせも出ないことだ。手元で--logLevel infoを付けてビルドしても、外された記事についての警告は1行も出なかった。出力の最後に表示されるページ数の表が少し小さくなっているだけで、ビルド自体は成功として終わる。GitHub Actionsのログも同じで、緑色のチェックが付いたまま記事だけが無い、という状態になる。

さらに、静的サイトなので、外された記事はあとから勝手に現れたりしない。朝7時にpushしたビルドで外れた記事は、9時を過ぎても、次に誰かがpushしてビルドが走るまでサイトに出てこない。昼に別の記事を足したら、朝の記事が一緒に現れた、という形で気づくことも多いと思う。

手元で再現する。5つの日付の書き方を比べた

再現には、テーマを入れない空のサイトをhugo new siteで作り、記事を5つ置いた。テンプレートは記事のタイトルと日付を並べるだけのものにしている。日本時間10月16日の7時31分(UTCでは15日の22時31分)に、timeZoneの有無でどれがビルドに残るかを見た。

記事front matterのdatetimeZoneなしtimeZone = 'Asia/Tokyo'
A2026-10-16外れる出る
B2026-10-16T07:00:00外れる出る
C2026-10-16T07:00:00+09:00出る出る
D2026-10-15出る出る
E"2026-10-16"(文字列で囲む)外れる出る

timeZoneが無いとき、AとBとEは未来扱いになり、出力されたのはCとDの2本だけだった。引用符で囲んで文字列として書いても、扱いは日付のときと変わらない。+09:00を付けたCだけは、設定に関係なくいつでも正しく日本時間として読まれる。前日の日付のDは、UTCで読んでもすでに過去なので問題にならない。

timeZone = 'Asia/Tokyo'を足すと、5本すべてが日本時間として読まれ、全部ビルドに残った。どの記事がどの時刻として読まれているかは、hugo list allを実行すると、記事ごとのdateとpublishDateが一覧で出るので一目で分かる。今回の実験では、publishDateを書いていない記事は、dateと同じ値がそのまま入っていた。未来扱いで外れている記事だけを見たいときはhugo list futureが便利だ。

時刻を変えて確かめたいときは、--clockというオプションでHugoが使う現在時刻を差し替えられる。hugo list future --clock 2026-10-15T23:59:00+09:00のように渡すと、その時刻に実行したときに未来扱いになる記事が分かる。朝の時間帯の挙動を、わざわざ朝まで待たずに確かめられるので、設定を変えたあとの確認にも使える。

直し方は3つ。設定に1行足すのがいちばん確実

いちばん手早いのは、hugo.tomlに次の1行を足すことだ。タイムゾーン名はAsia/Tokyoのように、地域名で書く。

timeZone = 'Asia/Tokyo'

これで、オフセットの無い日付はすべて日本時間として読まれる。すでに書いた記事を1本ずつ直す必要もない。このtimeZoneの設定はHugo v0.87.0で加わったもので、それより古い版を使っている場合は、まず版を上げることになる。設定を確かめたいときはhugo configを実行し、出力にtimezone = 'Asia/Tokyo'の行があるかを見ればよい。

2つ目は、記事のfront matterに毎回オフセットまで書く方法だ。date: 2026-10-16T07:30:00+09:00のように書いておけば、設定に関係なく正しい時刻になる。このブログも、設定にtimeZoneを入れたうえで、記事の日付には+09:00を付けて書いている。設定と記事の両方で日本時間をはっきりさせておくと、テーマを入れ替えたり設定ファイルを作り直したりしたときにも崩れにくい。

3つ目は、設定ファイルを触らずに環境変数で渡す方法だ。HugoはHUGO_で始まる環境変数を設定として読むので、HUGO_TIMEZONE=Asia/Tokyoを付けてビルドすると、設定ファイルに書いたときと同じく日本時間で解釈された。先ほど書いたとおりTZでは効かないので、名前を取り違えないようにしたい。ビルド環境ごとにタイムゾーンを変えたい事情がなければ、設定ファイルに書くほうが分かりやすいと思う。

逆に、buildFuture = trueにして、未来の記事も全部出してしまうという直し方は勧めない。記事は出るようになるが、日付は9時間ずれたまま表示され、予約投稿のつもりで先の日付を入れた記事まで公開されてしまう。症状は消えても原因は残る直し方だ。

GitHub Actionsでデプロイするときに確かめておくこと

手元のリポジトリからcommitしてpushし、GitHub上のリポジトリから取り込む流れを矢印で示した図
手元でcommitし、GitHubへpushする流れ。GitHub Pagesでは、このpushをきっかけにビルドが走るPhoto: G.prof / CC BY-SA 4.0

GitHub Pagesに置くときは、mainブランチへのpushをきっかけにGitHub ActionsでHugoを動かし、できたpublicフォルダを公開する形が一般的だ。このとき、ビルドを実行するのはGitHubのサーバーで、手元のパソコンではない。サーバー側の時計もUTCで動いている。手元で確かめたときは出ていた記事が、デプロイ後のサイトでは消えている、というずれが起きるのは、ビルドした時刻と場所が違うからだ。

手元とサーバーで結果をそろえるには、まずHugoの版を合わせておく。ワークフローの中でHUGO_VERSIONのような変数に版を書き、そのバイナリを取ってきてインストールする作りにしておけば、手元とサーバーで別の版が動くことを避けられる。このブログでは、手元もワークフローもv0.166.0にそろえている。そのうえでtimeZoneを設定ファイルに書いておけば、どちらでビルドしても日付の解釈は同じになる。

予約投稿として未来の日付の記事を置いておきたい場合は、もうひとつ準備がいる。静的サイトは、ビルドが走らないかぎり中身が変わらない。指定した日時を過ぎても、誰もpushしなければ記事は出てこない。ワークフローのon:にscheduleを足して、毎日決まった時刻にビルドが走るようにしておくとよい。

on:
  push:
    branches: ["main"]
  schedule:
    - cron: "5 15 * * *"   # UTCの15時5分、日本時間の0時5分
  workflow_dispatch:

ここでのcronの時刻もUTCで書く決まりになっている。日本時間の0時5分に動かしたいなら、9時間引いてUTCの15時5分と書く。front matterの日付と同じく、ここでも9時間のずれを頭に入れておく必要がある。workflow_dispatchも入れておくと、GitHubの画面から手動でビルドをやり直せるので、記事が出ていないことに気づいたときにすぐ試せる。

記事が出ないときに見る順番

経度に沿って縦の帯で世界を区切り、各地域の時差を色分けした1962年の世界時刻帯図
1962年に英国の水路部が刊行した世界の時刻帯図。同じ瞬間でも、場所によって日付が違うPhoto: United Kingdom Hydrographic Office / Public domain

記事がサイトに出てこないとき、原因は日付だけではない。下書きのまま、ファイルの置き場所の違い、テンプレート側の条件など、ほかにも疑うところはある。自分は次の順番で確かめるようにしている。上から順に、手間が少なく、よく当たるものを並べた。

  1. front matterにdraft: trueが残っていないか。hugo list draftsに出てくれば下書き扱いになっている。
  2. hugo list futureにその記事が出てこないか。出てくれば、日付が未来として読まれている。
  3. hugo list allで、その記事のdateとpublishDateが何時何分として読まれているか。末尾がZや+00:00ならUTCとして読まれている。
  4. hugo configの出力にtimezoneの行があるか。無ければ設定ファイルに書いたtimeZoneが読まれていない。
  5. expiryDateを書いていないか。有効期限が過ぎた記事はhugo list expiredに出る。こちらも同じタイムゾーンで解釈される。

1から3までで、たいていの原因は見つかる。特に2は、日付の問題かどうかを一度で切り分けられるので、最初に試す価値がある。何も出てこなければ、日付以外、たとえばセクションの置き場所やテンプレートの絞り込み条件を疑う段階に進めばよい。

もうひとつ覚えておきたいのは、記事に表示される日付も同じ解釈で決まることだ。timeZoneが無いまま2026-10-16T07:00:00と書いた記事は、出たとしても「16時」の記事として扱われ、記事一覧の並び順やRSSの日時もそれに従う。記事が消えるという目立つ症状が出なくても、日付のずれは静かに残る。

まとめ

Hugoで当日の記事が出ない問題は、時刻やオフセットのない日付がUTCで読まれることと、未来の記事を黙って外すbuildFutureの既定が重なって起きる。日本では、日付だけの記事が毎日0時から9時まで未来扱いになり、その時間帯にビルドすると記事が外れる。エラーも警告も出ないので、ログを見ても気づけない。

対策やること効く範囲向いている場面
設定に書くtimeZone = 'Asia/Tokyo'オフセットの無い全部の日付まず最初にやる
記事に書くdate: ...T07:30:00+09:00その記事だけ設定と併用して崩れにくくする
環境変数で渡すHUGO_TIMEZONE=Asia/Tokyoそのビルドだけ設定ファイルを触れないとき
定期ビルドワークフローにscheduleを足す予約投稿の記事先の日付の記事を置いておくとき

自分は、設定の1行と記事の+09:00を両方入れ、確認にはhugo list futureを使うことにした。日付の書き方を1つ変えるだけで済む問題だが、知らないと何時間も迷う。朝に書いた記事が出てこないときは、まずこの記事の表を思い出してほしい。

関連記事