天上天下唯我独尊備忘録雑記メモ覚書
2024.08.02
HUGO+GitHubPages+GitHubActionsでWebサイトを公開しよう
#HUGO #GitHubActions

Webページ作成はHTMLべた書きでめんどくさくて時間もかかるし大して面白くない、レンダリングエンジン作る方が面白そう、とそんなイメージを持っていましたが、
HUGOとGitHubActionsを使って公開まで自動化ができるというのを見て興味が出る。
この記事は実際にデプロイして公開までやってみた備忘録になります。
現時点でWebの知識もお金も全くないです。


公開先をGitHub PagesにするかNetlifyにするかで迷いましたが、いろいろとめんどくさくないGitHubPagesに決定。
ブログっぽいものとして最低限必要な要素を解説していきます。


注意点ですが、GitHub Pagesはアフィリエイトなどの運用は恐らく認められていません。


GitHubでソースコード公開してるので、気になったら参考にどうぞ。
https://github.com/OrangeCocoa/Hugo_OrangeCocoa.github.io


クイックスタートガイドはここ


HUGOでよく使うGoテンプレートの基本構文はここ


(リンクを英語ドキュメントから日本語ドキュメントに差し替えましたが、一部翻訳ページがないものもあります)


目次


HUGOのインストール

ここからDL
Windowsなので

winget install Hugo.Hugo.Extended
でインストール。

環境変数に登録までされるので、これが一番楽。


HUGOのセットアップ

コマンドプロンプトを開き、

# カレントディレクトリにHugoのプロジェクトが新規作成される
hugo new site [Hugoプロジェクト名]

cd [Hugoプロジェクト名]

# HTMLのビルド
hugo

# ローカルサーバー起動
hugo server

hugo new site でいくつかフォルダが作られるが、よく使うのはcontent, layouts, staticの三つ。
またhugo.tomlはプロジェクトの設定ファイルになるので、これもよく編集します。


フォルダ構成はこんな感じ。

projectaacdlsthrsoaathucsntyaeghetaotmoeteuie.tsntcstytsopmels
  • archetypes・・・Markdown記事のテンプレート保存先
  • assets ・・・グローバルリソース
  • content ・・・Markdownで書いた記事の保存先、HTMLからアクセスする場合に画像や動画
  • data ・・・データファイルの保存先、CSVやJSONなど
  • layouts ・・・HTMLファイルや分割ファイルの保存先
  • static ・・・CSSや画像ファイルの保存先、Markdownからアクセスする静的リソースはここに入れる
  • themes ・・・Hugoのテーマの保存先
  • hugo.toml ・・・Webサイトの設定ファイル、バージョンによって名前がconfig.tomlだったりする

プロジェクトのディレクトリで hugo コマンドを実行すると、リリース用のURLを設定したHTML群がpublicフォルダにビルドされます。
今回はGitHub Actionsにビルドを任せるので使わなくてもOK。


hugo server コマンドを実行すると、URL接続先をローカルサーバ用にしたHTML群がpublicフォルダにビルドされ、1313番ポートが開放されます。
この状態で**http://localhost:1313/**にアクセスすると、レイアウトなどの確認が可能になります。
また、ローカルサーバーモード起動中はホットリロードが有効なので、MarkdownやHTMLを編集すると、保存した段階でビルドが走り即座に反映が可能。
HTMLを編集したときに限り稀にビルドされないため(キャッシュあるからいいや的な何かが働いてる?)、なんか思ってたのと違う場合はCtrl+Cで一度サーバーを落として再度ビルドをかけてみるといいかも。


hugo.toml

作成されたhugo.tomlファイルにWebページの情報を記載します。

baseURL = 'https://OrangeCocoa.github.io'
languageCode = 'ja-jp'
title = 'ぶろぐ'

その他の設定項目についてはこちら、マークアップについてはこちらを参照。


header.html, footer.html, style.css

分割定義するため、ヘッダとフッタ用のHTMLファイルを作成します。


layouts/partialsというフォルダを作成し、header.htmlfooter.htmlを作成。

<!DOCTYPE html>
    <html lang="ja">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no">
        <link rel="stylesheet" href="{{ .Site.BaseURL }}/style.css">
        <title>(仮)</title>
    </head>
    <body>
        <div>
            <header>
                <div>(仮)</a></div>
            </header>
        </div>
        <footer>
            <div class="footer">© 2024 (仮) All rights reserved.</div>
        </footer>
    </body>
</html>

.Site.BaseURLと書いている箇所がありますが、
html内のグローバルスコープはPageなので、
{{ }} で囲い、ドット演算子を用いることでPage内の関数呼び出しや変数の参照が可能。
この場合はhugo.tomlで指定したbaseURLを呼び出しています。


続いて、staticフォルダ内にstyle.cssを作成。

html {
    scroll-behavior: smooth;
}

body {
    background-color : #f5f5f5;
}

header {
    padding-top : 1%;
    padding-bottom : 1%;
}

div {
    display: block;
    unicode-bidi: isolate;
    padding-top : 5px;
    padding-bottom : 5px;
}

.footer {
    text-align: center;

    /* 範囲選択を禁止する*/
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

CSSに関してはHUGO固有の挙動などはないので、適宜必要になった時に調べて追記していきます。


範囲選択禁止部分のベンダープレフィックスについてはこちら
(ブラウザに使われているレンダリングエンジンによって挙動が異なるので、全部指定してるって認識でOK)


シングルページとリストページの作成

HUGOにおいて、Webページに必要なHTMLファイルはテンプレートによって自動生成されます。


layouts/_default/single.html

まずシングルページテンプレートを作成します。

{{ partial "header.html" . }}
        <div>
            <div>{{ .Date.Format "2006.01.02" }}</div>
            <div>{{ .Title }}</div>
            <div>{{ .Content }}</div>
        </div>
{{ partial "footer.html" . }}   

layouts/_default/list.html

次にリストテンプレートを作成します。

{{ partial "header.html" . }}
        <div>
            <div>記事一覧</div>
            {{ range (.Paginator 10).Pages }}
            <a>{{ .Date.Format "2006.01.02" }} </a>
            <a href="{{ .RelPermalink }}">{{ .Title }}</a><br>
            {{ end }}
        </div>
{{ partial "footer.html" . }}

contentフォルダ下に新しくフォルダを作ってページ作成すると、フォルダごとのシングルページを管理できるindexページが作成されます。


content/[フォルダ名]/[記事名].md

最後にシングルページの内容をMarkdownで記述します。
single.htmlの .Contentによってレンダリングされます。


---
title: "タイトル"
date: "2024-08-02"
draft: false
categories: [ "" ]
tags: [ "" ] 
---

### 見出し
本文

—で囲まれている部分は、ページのメタデータでフロントマターと呼ばれます。 いろいろ設定できるほか、ユーザー定義もできるため、ページの細かいカテゴリ分けや属性として利用できます。


文字列や数値、bool値、日付といったものはそのまま任意の変数を追加するだけで利用でき、HTMLから **.Param.[名前]**で取得できます。 くわしくはこちら


文字列配列を設定する場合はタクソノミー設定を行ないます。 タクソノミーで追加された値は、public/tags/~のように一覧化され、 html内で .Site.Taxonomies.tagsと呼ぶことで、定義された文字列がすべて取得できます。


TOPページの作成

layouts/index.html

{{ partial "header.html" . }}
<div id="top_right">
    <div>新着記事</div>
    <div>
        <!--全記事をループで回す-->
        {{ range first 10 .Site.RegularPages.ByDate.Reverse }}
        <a>{{ .Date.Format "2006.01.02" }} </a>
        <div>
            <a href="{{ .RelPermalink }}" style="font-size:22px;">{{ .Title }}</a>
        </div>
        <br>
        {{ end }}
    </div>
</div>
{{ partial "footer.html" . }}

{{ partial "header.html" . }}
で分割HTMLの呼び出しを行なっています。
(phpであれば<?php include "header.html" ?>と記述するところ)


.Site.RegularPagesを使うことで、シングルページのみをフォルダ関係なく全て取得できるので、それをrangeループでそれぞれのインスタンスを取得します。
.Site.Pagesを使うとリストページも取得する)


.Date.Formatでフロントマター指定した"date"の項目をフォーマット指定で出力します。


.RelPermalinkでページのURLを取得しhref指定、
.Titleでフロントマター指定した"title"を出力します。


GitHub リポジトリの作成と、GitHub Actionsへのフック

GitHubにHUGOのデプロイ先と、HUGOプロジェクトの二つのリポジトリを作ります。
このとき、HUGOのデプロイ先はどうもGitHubアカウント名と同じ名前を使わないと、URLが**[アカウント名].github.io/[ブランチ名]の構成になるらしい。 アカ名はOrangeCocoaなので、ブランチ名はOrangeCocoa.github.io**。
これでURLはhttps://OrangeCocoa.github.ioになる想定。
気に入らなければサイト名のGitHubアカウントを新規に取得してしまってもいい。


エンジニアならリポジトリの作り方くらい知ってると思いますが、一応手順を記載します。


  1. WebのGitHubで空のHUGOデプロイ先と、HUGOプロジェクトのリモートリポジトリを作成。

  2. Forkを使ってる場合、File > Init New RepositoryでHUGOプロジェクトのフォルダを選択してローカルリポジトリを作成。(Hugoデプロイ先はいらない)

  3. HUGOプロジェクトのローカルリポジトリタブを選択している状態で、右上にあるConsoleボタンを押してコマンドプロンプトを開き、下記コマンドを実行。
    その後プッシュすればリモートブランチ作成コミットとなります。


git remote add origin https://github.com/[アカウント名]/[リポジトリ名]


それが済んだらデプロイのためのSSH鍵を登録していきます。


コマンドプロンプトなどで

ssh-keygen -t ed25519 -f blog_ssh
を実行して適当に公開鍵と秘密鍵を作成。


sshkey


次に公開鍵(.pubとついてる方)をHUGOのデプロイ先リポジトリに登録。
メモ帳などのテキストエディタで開いてコピペ。


このとき、Allow write accessにチェックを入れるのを忘れずに。
書き込み権限がなくてデプロイに失敗します。


deploykey


続いてHUGOプロジェクトのリポジトリに秘密鍵を登録。
ここで書いたTitleが実行ファイルのシークレット指定に必要になってくるのでコピペ。


secretkey


その後、プロジェクト直下から .github/workflows/config.yml ファイルを作成して以下の実行処理を記載。


name: github pages

on:
  push:
    branches:
      - master  # ブランチ名に対してデプロイを実行

  # Actionタブからこのワークフローを手動で実行できる
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          submodules: true  # サブモジュールに登録されたテーマもフェッチするか
          fetch-depth: 0    # 全タグ・全ブランチ・全履歴を取得する

      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2
        with:
          hugo-version: 'latest'

      - name: Build
        run: hugo --minify

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        with:
          deploy_key: ${{ secrets.DEPLOY_SECRET }}
          external_repository: OrangeCocoa/OrangeCocoa.github.io
          publish_branch: master

上記が終わったらHUGOプロジェクトをコミット。


添付画像のとおりビルドが通れば成功です。
ちなみに自分は何度か失敗しました。(デプロイエラー解決に続く)



デプロイエラー解決

「Waiting for a runner to pick up this job…」

Actions上で上記のように表示されてジョブがスタートしない場合、OS指定がミスってるのでubuntu-latestを指定。


Error: fatal: No url found for submodule path ‘public’ in .gitmodules

のエラーでBuildが止まった場合、publicやresourcesフォルダなどのHugoが生成するフォルダがデプロイ先リポジトリに含まれているので、削除しておく。


Error: Unable to locate config file or config directory. Perhaps you need to create a new site.

が出た場合、hugo-version指定が古くてhugo.tomlでなくconfig.tomlを探しに行ってるので、’latest’指定しておく。


ERROR: The key you are authenticating with has been marked as read only.

が出た場合、デプロイキーに書き込み権限がないのでデプロイキーを登録しなおすこと。


Error: Action failed with “not found deploy key or tokens”

が出た場合、シークレットが登録されていない。


Error loading key “/home/runner/.ssh/github”: error in libcrypto

が出た場合、シークレット登録の際、
—–BEGIN OPENSSH PRIVATE KEY—–
—–END OPENSSH PRIVATE KEY—–
を削って登録した疑惑。全部コピペすること。


画像の挿入

普通にWebに上がってる画像をURL指定で参照してもいいですが、手持ちの画像を表示させたいときはstaticフォルダに画像を格納します。
今回はimagesフォルダを作り、そこに必要な画像を入れていきます。


HUGOのMarkdownでは
![name](/images/~~~.png)
と記述します。


また、元サイズから変更したい場合は \{\{< figure src="/images/~~~.png" width=1200 >\}\} のように記述すると、figureタグに変換してくれます。


コメント欄の追加

Disqusutterancesが使えそうです。
utterancesが楽そうなのでこっちに。
ログイン必須ですがコメントがGitHub issue に保存される模様。
(Disqusもログイン必須)


utterances appをインストール。


その後、single.htmlのフッター直前に以下のコードを挿入するだけでコメントが表示されます。


<script src="https://utteranc.es/client.js"
        repo="OrangeCocoa/OrangeCocoa.github.io"
        issue-term="pathname"
        theme="github-light"
        crossorigin="anonymous"
        async>
</script>

Disqusも導入してみたところ、コンソール画面でコメントの集計や削除ができるといった感じで、 しっかり運営するならDisqusの方がよさそうですが、GitHubで完結する楽さには抗えませんでした。


追記: issueではなくdiscussionsにコメント蓄積するgiscusが使えそうだったので、こっちにしました。


リポジトリ設定のGeneralからDiscussions機能を有効化。
※デプロイ先のリポジトリです。


discussions


次にここから対象のリポジトリへgiscusのインストールを行なう。


その後、giscusの設定ページから貼り付けるためのコードを生成して、utterancesと同じようにフッターに記述します。
カテゴリはAnnouncementsを指定した方がいいらしい。
設定した項目によるが、だいたいこんな感じ。


<script src="https://giscus.app/client.js"
        data-repo="[リポジトリを記述]"
        data-repo-id="[リポジトリIDを記述]"
        data-category="[カテゴリ名を記述]"
        data-category-id="[カテゴリIDを記述]"
        data-mapping="pathname"
        data-strict="0"
        data-reactions-enabled="1"
        data-emit-metadata="0"
        data-input-position="top"
        data-theme="preferred_color_scheme"
        data-lang="ja"
        data-loading="lazy"
        crossorigin="anonymous"
        async>
</script>

Markdownの変換挙動をカスタマイズする {custom-markdown}

layouts/_default/_markupフォルダ内にrender-{kind}.htmlのファイルを作成することで、レンダリング(MarkdownをHTMLコードに変換することをそう呼ぶらしい)の挙動を変えることができます。
詳しくはここ


別タブでリンクを開くためには、どうやらレンダリング処理をオーバーライドする必要がある模様。
~(よく使うんだから最初から使えるようにしとけ)~


layouts/_default/_markup/render-link.html
を新規作成し、下記コードを記載。
インデントしたら意図しない空白が入るようにレンダリングされたので一行にまとめました。
誰か原因教えて。


<a href="{{ .Destination | safeURL }}" {{ with .Title }} title="{{ . }}"{{ end }} {{ if strings.HasPrefix .Destination "http" }} target="_blank" rel="noopener"{{ end }}>{{.Text}}</a>

Markdown内でCSS定義したスタイルを使う

hugo.toml内に下記を追記。


[markup.goldmark.parser.attribute]
block = true

さらにstyle.css内で適用したいスタイルを定義。

.deco_background_red
{
    color: #fff; /* 白文字 */
    background-color: #ee6666; /* 赤背景 */
    padding: 0.1em 0.2em 0.1em 0.2em;
}

これでスタイル指定したい箇所の下に{.deco_background_red} と書けば、CSSに定義されたスタイルを適用することができます。


Goldmarkについてはこちら、そしてリポジトリ
GoldmarkはMarkdownをレンダリングする際に使われるパーサーで、以前はblackfridayが使われていました。
blackfridayを使った説明記事を見かけたら回れ右推奨。


Markdown内でmermaid記法を使う

ここ参照。 layouts/_default/_markup/render-codeblock-mermaid.html
を新規作成し、下記コードを記載。


<pre class="mermaid">
	{{- .Inner | safeHTML }}
</pre>
{{ .Page.Store.Set "hasMermaid" true }}

既存ファイルの末尾あたりに下記コードを挿入。
footer.htmlに入れました。


{{ if .Store.Get "hasMermaid" }}
<script type="module">
	import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs';
	mermaid.initialize({ startOnLoad: true });
</script>
{{ end }}

これだけでMarkdown挿入が可能に。


``` mermaid
sequenceDiagram
    participant Alice
    participant Bob
    Alice->>John: Hello John, how are you?
    loop Healthcheck
        John->>John: Fight against hypochondria
    end
    Note right of John: Rational thoughts <br/>prevail!
    John-->>Alice: Great!
    John->>Bob: How about you?
    Bob-->>John: Jolly good!
```
sequenceDiagram
    participant Alice
    participant Bob
    Alice->>John: Hello John, how are you?
    loop Healthcheck
        John->>John: Fight against hypochondria
    end
    Note right of John: Rational thoughts 
prevail! John-->>Alice: Great! John->>Bob: How about you? Bob-->>John: Jolly good!

Markdown内でGoAT記法を使う

デフォルトでサポートされているので、特に何も設定しなくても使えます。 詳しくはここ


そのまま書くと現在のdivサイズに合わせてドデカく表示されるので、 記事内でサイズコントロールしたい場合はMarkdown内でCSS定義したスタイルを使うと合わせてスタイル指定してあげると良さそう。


<div style="width: 800px">

```goat
+-------------------+                           ^                      .---.
|    A Box          |__.--.__    __.-->         |      .-.             |   |
|                   |        '--'               v     | * |<---        |   |
+-------------------+                                  '-'             |   |
                       Round                                       *---(-. |
  .-----------------.  .-------.    .----------.         .-------.     | | |
 |   Mixed Rounded  | |         |  / Diagonals  \        |   |   |     | | |
 | & Square Corners |  '--. .--'  /              \       |---+---|     '-)-'       .--------.
 '--+------------+-'  .--. |     '-------+--------'      |   |   |       |        / Search /
    |            |   |    | '---.        |               '-------'       |       '-+------'
    |<---------->|   |    |      |       v                Interior                 |     ^
    '           <---'      '----'   .-----------.              ---.     .---       v     |
 .------------------.  Diag line    | .-------. +---.              \   /           .     |
 |   if (a > b)     +---.      .--->| |       | |    | Curved line  \ /           / \    |
 |   obj->fcn()     |    \    /     | '-------' |<--'                +           /   \   |
 '------------------'     '--'      '--+--------'      .--. .--.     |  .-.     +Done?+-'
    .---+-----.                        |   ^           |\ | | /|  .--+ |   |     \   /
    |   |     | Join        \|/        |   | Curved    | \| |/ | |    \    |      \ /
    |   |     +---->  o    --o--        '-'  Vertical  '--' '--'  '--  '--'        +  .---.
 <--+---+-----'       |     /|\                                                    |  | 3 |
                      v                             not:line    'quotes'        .-'   '---'
  .-.             .---+--------.            /            A || B   *bold*       |        ^
 |   |           |   Not a dot  |      <---+---<--    A dash--is not a line    v        |
  '-'             '---------+--'          /           Nor/is this.            ---
```

</div>
&AMSioiqfbBxujoea(-xdra>efR>coCnuob(nr))dneeJdrosinNoRDtoiuaangddloitneDiagonalsCVuerrvteidcalnotANC:ouldrriAa/Ivnsineehstd-e-trlBihiision'sreqn.u*oobttoelasd'*lineDonSee?arc3h

ちなみにこれをHTMLにレンダリングしたものをみると、とんでもないコードができあがっている。


Google検索に引っ掛けるには、Webページのサイトマップを作成し、Google Search Consoleで登録する必要があります。 サイトマップはHUGOによってデフォルトで作成されるようになっているので、特に設定することはありません。
カスタマイズする場合はこちらを参照。


まずはGoogle Search Consoleの登録。
URLプレフィックスの方に登録するサイトのURLを入力し、続行します。


sitemap01


Google Analyticsに登録していなかったり、DNSレコードが関連付けられていない場合は、サイトの所有権が確認できていない状態なので、確認をとる必要があります。
ファイルをダウンロードし、デプロイ先のリポジトリ直下にhtmlファイルを配置したあと、確認ボタンを押してください。


sitemap02


そのままだとHTMLがファイルが消えた場合に関連付けができなくなってしまうので、サイト内にメタタグを埋め込みます。
設定からHTMLタグを選択し、表示されたコードをコピー。
<head>タグ内にペーストし、サイトに適用されたら確認ボタンを押します。


sitemap03


https://[ユーザー名].github.io/[リポジトリ名]/sitemap.xmlのURLでサイトマップが作成されていると思うので、
Google Search Consoleのサイトマップから、サイトマップのパスを入力して送信します。


sitemap04


これで2, 3日待てばGoogle検索に引っかかるようになります。