2024年2月23日 追記
GitHub Actions内のコマンドにミスがあったので修正しました。
run: github-issue-cms --token=${{ secrets.GH_TOKEN }}
run: github-issue-cms generate --token=${{ secrets.GH_TOKEN }}
こんにちは。
今回はGitHubをHugoのCMSとして活用してみました。
今回作成したツールは以下のリポジトリで公開しています。
Hugo + Headless CMS
私のブログではHugoをSSGとして利用しています。
新しく記事を執筆するときは、執筆環境にサイト管理用のリポジトリやVSCode、Hugoを用意してから執筆することになります。
この作業は一度やれば済むので、別にそこまで辛くはないのですが、問題は画像を配置するときです。
VSCodeを使って執筆している時に、新規に画像を追加すると以下の手順が発生します。
これを画像を追加するたびに行うのは非常に面倒です。
そこで、ヘッドレスCMSの登場です。これを利用すれば、QiitaやZennのようにクリップボードにある画像をそのまま配置できたり、画像をWordPressのように管理できたりします。
Hugoと相性の良いヘッドレスCMSを検索してみるとForestryやNetlify CMSなどがヒットします。
しかしながら、ForestryはTina CMSというプロジェクト変わっていたり、Netflify CMSはデプロイ先が限られたりとやや面倒そうです。
実際にTina CMSについては使用してみましたが、 UIが好みではなく、設定についてもやや限られている印象でした。(私が使いこなせてない可能性も大いにありますが...)
ここで、私は欲しいヘッドレスCMSは以下のような特徴を持つものです。
- DBMSを必要としない
- いつでも執筆環境にアクセスできる
- ノーコストで運用ができる
GitHubという選択肢
みなさんご存知の通り、GitHubのIssueには優秀なMarkdownエディタがあります。
このエディタはクリップボードにある画像も貼り付けることができます。
テンプレート機能を利用すれば、以下のように記事の雛形を登録しておくことも可能です。
そして、GitHubはAPIを提供しており、GitHub上のデータを自由に利用できます。
ということで、CMSに必要なフロント部分としてGitHubを活用し、記事の生成部分を自分で作ります。
作ったもの
今回作成したものは、GitHub上のIssueを記事として扱います。
実行時にCloseされているIssueの本文を記事のコンテンツとしてファイルに保持します。
タグやカテゴリについては、それぞれラベルとマイルストーンが対応します。
Hugoが提供するフロントマター機能については、Issueの先頭にコードブロックを記述することで任意のコンテンツを挿入できるようにしています。
また、画像についても、IssueをMarkdownとして保存する際にダウンロードとリンクの張り替えを行います。
既存のHugoプロジェクトに組み込む
今回作成したものは、CIに組み込むことを想定しています。この記事の場合、データをすべてGitHub上に保管しており、CIとしてGitHub Actionsを利用しました。
GitHub Actionsで利用する際に、Issueへのアクセス権を持ったトークンを発行しておきます。
https://github.com/settings/tokens
から新規Classicトークンを発行します。この時にrepoスコープに権限を与えるようにしてください。
発行したトークンをブログを管理しているリポジトリのシークレットにGH_TOKEN
という名前で登録します。
次にリポジトリのルートディレクトリにgic.config.yaml
というYAMLファイルを作成します。
github.username
に自分のユーザ名、github.repository
に対象のリポジトリ名を記します。
hugo.url.images
には、Hugoで画像を配置するディレクトリ名を書きます。
github:
username: 'your-name'
repository: 'your-repository'
hugo:
url:
images: '/your-images-directory'
最後に以下のようなワークフローを定義します。
name: Go
on:
push:
branches: [ "main" ]
issues:
types: [reopened, closed]
pull_request:
branches: [ "main" ]
workflow_dispatch:
permissions: write-all
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21.4'
- name: Install Tools
run: go install github.com/rokuosan/github-issue-cms@latest
- name: Generate
run: github-issue-cms generate --token=${{ secrets.GH_TOKEN }}
- name: Auto Commit
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: "ci: :memo: Update articles"
- name: Clouflare Pages Webhook
run: |
curl -X POST ${{ secrets.CF_WEBHOOK_URL }}
私はデプロイにCloudflare Pagesを利用しているため、ジョブの最後にCloudflareのデプロイ用Webhookに対してPOSTするようにしています。
Issue を書く
ワークフローを登録しているリポジトリで、Issueを書きます。
このとき、カスタムフロントマターを利用しやすくするために、Issueのテンプレートを作成しておくことをお勧めします。
リポジトリに.github/ISSUE_TEMPLATE
という名前でディレクトリを作成し、以下のようなMarkdownを作成します。
---
name: Article
about: Template for a new article
title: ''
labels: ''
assignees: ''
---
```
slug: 'your-article-slug'
# url: '/articles/your-article-url'
```
## Title
デプロイの確認
最後に正常にデプロイがされているか確認します。
GitHubとCloudflareでビルドにかかる時間はそれぞれ30秒程度なので、Issueをクローズしてから1分ほどでデプロイが完了します。
作成したツールのインストールに時間がかかっているみたいなので、ビルド済みのものをリリースすれば時間短縮ができそうですね...。
終わりに
GitHubで記事が書けるとなると、今までの心理的ハードルもさがり、筆も走るような気がします。
また、外部サービスを利用しないので料金がかからないのでお財布にも優しいですね。その代わりGitHub APIの仕様変更などがあれば対応しなければなりませんが...。