Hugo × Vercel × tcardgen で動的な OGP 画像を設定
ざっくり
Hugo で作成している静的サイトに OGP 画像を動的に設定したくなったので, 色々と試行錯誤しました.
思いのほか骨が折れたので, 結果をまとめることにしました.
変更前
変更後
OGP 画像を作成するツール
tcardgen を使いました.
tcardgen はテンプレートと設定を用意すれば, 元になるマークダウンファイルのフロントマターから情報を抜き出して OGP 画像を作成してくれるツールです.
OGP 画像を生成するには サイトのOGP画像を自動生成する で紹介されるように他にも色々あるようです.
そんな中で tcardgen を選んだ理由は, テンプレートとなる画像と設定だけ用意すれば OGP 画像を作ってくれるため簡単そうだったからです.
しかし, 後述するように, Vercel との相性を考えると, 上のサイトで紹介されるような node-canvas を使った方法の方が楽ちんだったかもしれません.
tcardgen をカスタマイズ
使い方は簡単な tcardgen ですが, 私の用途からすると困ったところもありました.
そこで, 自分用に少しカスタマイズを行いました.
なぜカスタマイズしたか
tcardgen の困ったところは, フロントマターに必須の項目があることです.
例えば, author
や categories
が必須であり, この2つが自分にとっては厄介でした.
まず, author
については, 自分しか記事を書かないので, author: qawatake
で固定したいです.
しかし, tcardgen では author
のデフォルト値を指定することはできません.
つまり, すべての記事に author: qawatake
を記載する必要があります.
これはちょっと冗長です.
さらに, Obsidian → Hugo の運用方法 で説明したように, 自分は Obsidian から Hugo 用に記事を自動生成しているため, あらかじめ Obsidian 内のファイルで author:qawatake
を記載しなければいけなくなります.
まぁ大した手間ではないので記載してもいいのかもしれないですが, 個人的には無駄な情報をノートに記載したくないので, できればファイルで明示的に指定せずとも, author: qawatake
として処理してもらいたかったです.
次は, categories
です.
私はノートの分類に tags
を使用しています.
そして, tcardgen も実際 tags
に対応しています.
個人的には, この2つを設定する必要性が感じられません.
ノートのカテゴリー分類には categories
か tags
のどちらかで十分でしょう.
ということで, categories
を指定しなかった場合, 関連する処理をスキップして欲しくなりました.
カスタマイズ内容
↑で説明したような困ったことに対処するため, 次のように仕様を変更しました.
-
-a
フラグでデフォルトのauthor
を指定できるようにする -
categories
が指定されなかった場合, エラーを発生させず, その部分の処理をスキップする.
ありがたいことにそれほど変更せずとも, ↑の仕様を実現できました.
カスタマイズしたソースコードは GitHub - qawatake/tcardgen: Generate a TwitterCard(OGP) image for your Hugo posts. にあります.
Hugo 側の設定
これまではどの投稿も同じ OGP 画像を参照していましたが, これからはそれぞれ異なる画像を参照させなければいけません.
私の場合 ↓ のようなものを設定しました. (使用するテーマによって指定箇所は違います.)
...
{{- $tcardRelativeImagePath := path.Join "tcard" (print .File.BaseFileName ".png") -}}
{{ if .Params.meta_image }}
{{ with .Params.meta_image }}
<meta property="og:image" content="{{ . | absURL }}">
<meta property="og:image:url" content="{{ . | absURL }}">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:app:name:iphone" content="{{ $.Site.Title }}">
<meta property="twitter:title" content="{{ $.Param "title" }}">
<meta property="twitter:description" content="{{ $.Param "description" }}">
{{ end }}
{{ else if os.FileExists (path.Join "static" $tcardRelativeImagePath) }}
{{ with $tcardRelativeImagePath }}
<meta property="og:image" content="{{ . | absURL }}">
<meta property="og:image:url" content="{{ . | absURL }}">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:app:name:iphone" content="{{ $.Site.Title }}">
<meta property="twitter:title" content="{{ $.Param "title" }}">
<meta property="twitter:description" content="{{ $.Param "description" }}">
{{ end }}
...
テンプレート画像の作成
Figma を使って作成してみました.
Figma は初めてでしたが, 今回のような画像なら簡単に作れました.
デプロイ
これまでは, ただ GitHub に push すれば, Vercel が勝手に連携してデプロイが済んでいたわけですが, 動的に OGP 画像を生成するとなるとこれではうまくいかなさそうです.
というのも hugo
コマンドでビルドする前に, tcardgen
をインストールしてそれで OGP 画像を生成する, という処理が必要になるためです.
仕方ないので GitHub Actions を使って, OGP 画像の生成 & ビルド & Vercel へデプロイをすることにしました.
もし OGP 画像の生成に node-canvas を使っていれば, GitHub Actions などを使う必要はなさそうでした.
Vercel では package.json
で指定したビルドコマンドを実行できるので, わざわざ他所で OGP 画像を生成しなくても Vercel 上で生成までやってしまえるようです.
GitHub Actions や Vercel CLI の設定でそれなりに骨が折れたので, そのあたりをすっとばせるのはいいなぁと今では思っています.
Vercel CLI を使ってみる
いきなり GitHub Actions を使うと, エラー, push, エラー, push, ... と面倒なことになりそうだったので, まずは手元の vercel cli でデプロイをしてみることにしました.
いきなりエラー
検索しても同じ問題が発生している方が見つからず, 困りましたが原因は ↓ のようです.
- Hugo の設定で
enableGitInfo=true
にしている.- 投稿の最終更新日を Git のコミットログから取得するために必要.
- Vercel に CLI でデプロイする際には
.git
はアップロードされない. - Vercel 上で
hugo
ビルドしようとすると.git
が無いのでエラー.
ということで, 解決策としては,
-
enableGitInfo=false
にする- 投稿の最終更新日を設定できなくなるので却下.
- ローカルであらかじめビルドしておく
後者を採用する場合には, Vercel 上でビルドしないように, build command を空欄で上書きする必要があります.
GitHub Actions の設定
ひとまず, CLI でデプロイできるようになったのであとは GitHub Actions で自動化するだけです.
GitHub Actions の書き方は毎回忘れてしまい, 今回もそれなりに苦労したのですが, 最終的には ↓ のようになりました.
name: Deployment
on:
push:
branches:
- main
- staging
pull_request:
branches: [ main ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true # Fetch Hugo themes (true OR recursive)
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
- name: Setup Go environment
uses: actions/setup-go@v2.2.0
with:
go-version: ^1.17.0
- name: Install tcardgen
run: go install github.com/qawatake/tcardgen@latest
- name: Generate ogp images
run: tcardgen -a qawatake -f ogp/font -o static/tcard -c ogp/config.yaml obsidian/public/notes/*.md
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: '0.91.2'
extended: true
- name: Build (Preview)
run: hugo --baseURL "https://${{ secrets.PREVIEW_DOMAIN }}"
if: ${{ github.ref != 'refs/heads/main' }}
- name: Build (Production)
run: hugo --baseURL "https://${{ secrets.PRODUCTION_DOMAIN }}"
if: ${{ github.ref == 'refs/heads/main' }}
- name: Deploy to Vercel (Preview)
uses: amondnet/vercel-action@v20
if: ${{ github.ref != 'refs/heads/main' }}
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required
vercel-org-id: ${{ secrets.VERCEL_ORG_ID}} #Required
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID}} #Required
alias-domains: ${{ secrets.PREVIEW_DOMAIN }} #Optional
- name: Deploy to Vercel (Production)
uses: amondnet/vercel-action@v20
if: ${{ github.ref == 'refs/heads/main' }}
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }} # Required
vercel-org-id: ${{ secrets.VERCEL_ORG_ID}} #Required
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID}} #Required
vercel-args: '--prod' #Optional
本当は, tcardgen の処理などは, 別途カスタムアクションを定義したほうがよさそうですが, 疲れたので当面はこれでやっていこうと思います.
Discussion