Skip to main content

tcardgenとGitHub ActionsでHugoのブログのOGPを動的に作る

概要

markdownファイルからOGP画像を生成するLadicle/tcardgenを使い、Hugoで作っているブログで記事のmarkdownファイルをGitHubにpushすると、記事のOGP画像を動的に作るGitHub Actionsを作りました。動的というのは、markdownファイルのFront Matterからtitleなどを取り出してOGP画像を作るということです。

こんな感じです。

OGP画像にはうずらフォントを使いました。わたしの好きなかわいいフォントを選べてうれしいです。こういうのがモチベーションを上げるのです。

GitHub Actionsではなくローカルでtcardgenを実行してもいいですが、HugoのビルドもGitHub Actionsで行っている場合、markdownファイルを書いてpushするだけでビルドもOGP画像生成も自動でできちゃうのでとっても快適です。

このブログはHugo(テーマはanubis)のページバンドルとして、Leaf Bundleを採用しています。tcardgenをLeaf Bundleで動かすところとHugoのテーマにOGPを入れるのにちょっと苦戦したので、HugoのLeaf Bundleを採用しているサイトでtcardgenによってOGP画像をGitHub Actionsで作る方法をご紹介します。

大手ブログや技術ブログサイトなら何もしなくてもOGPを作ってくれるのでこういう手間はありませんが、自分だけのお城をHugoで好きなように建てていくのも悪くないです。使い慣れたエディタ(VSCode)とGitとGitHubで全てが完結できるのもいいですね。

技術構成

技術構成はこのようになっています。

もともと、Hugoのソースのリポジトリ(suzuna/blog-source)にpushすると、GitHub ActionsによってHugoのビルドを行い、生成物をHugoの公開用のリポジトリ(suzuna/blog)にpushするようにしていたのですが、このGitHub ActionsにtcardgenでのOGP画像生成を加えました。

Hugoのディレクトリ構成

content/以下はこのような構成になっています。content/posts以下に、記事を書いた日付をyyyymmddで先頭に付与したフォルダを作り、その中に記事をindex.mdで作成します。

content
├── about.md
└── posts
    ├── yyyymmdd-hoge-fuga-piyo
    │   └── index.md
    └── yyyymmdd-foo-bar-baz
        ├── images
        │   └── image01.jpg
        └── index.md

やったこと

tcardgenの出力OGP画像のファイル名の調整

tcardgenで、特定のディレクトリ以下にある全てのmarkdownファイルについてそれぞれOGP画像を作るなら、まずこちらを行います。

  • Goをインストールする
  • path/to/fontDir以下に、<font-name>-Bold.ttf, <font-name>-Medium.ttf, <font-name>-Regular.ttfのファイル名で3個のフォントを置く1
  • path/to/templateFileというファイル名でOGP画像のテンプレートファイルを置く

そして、tcardgenのREADMEのとおり以下を実行すれば、path/to/static/ogp直下に、path/to/content/posts/*.mdに該当する個々のmarkdownファイルから作成したOGP画像が出力されます。

go install github.com/Ladicle/tcardgen@latest
# 出力先の画像のディレクトリは事前に作る必要がある
mkdir -p path/to/static/ogp
tcardgen \
  --fontDir path/to/fontDir \
  --output path/to/static/ogp \
  --template path/to/templateFile \
  path/to/content/posts/*.md

ここで、出力される画像のファイル名は、Markdownファイルのファイル名と同じものになります。index.mdならindex.pngです。Leaf Bundleでは、記事のmarkdownファイルのファイル名は全てindex.mdのため、全ての記事のOGP画像はindex.pngとなり、ファイルが上書きされて1個しか生成されません。当たり前と言えば当たり前なのですが、ここでハマりました。

これを解消するために、content/posts/yyyymmdd-hoge-fuga-piyo/index.mdのOGP画像はhoge-fuga-piyo.pngとするように、1ファイルずつtcardgenを走らせます。

go install github.com/Ladicle/tcardgen@latest
mkdir -p static/ogp
files=`find ./content/posts/*/index.md -type f`
for f in $files; do
    slugname=`dirname ${f} | xargs -I@ basename @ | cut -c 10-`
    tcardgen --fontDir ogp/font --output "static/ogp/${slugname}.png" \
      --template ogp/tcard/template.png ${f}
done

このシェルスクリプトでcontent/posts/yyyymmdd-<適当な名前>/index.mdに合致する全てのmarkdownファイルのOGP画像がstatic/ogp/<適当な名前>.pngに出力されます。

ローカルで実行すればローカルで画像が生成されますが、今回はGitHub Actions上で行います。あとで説明します。

HugoのテンプレートにOGPを入れる

Step1でOGP画像は出力されますが、Hugoのテーマで対応していなければURLが貼られたときにOGP画像が表示されません。

OGP画像とOGPのdescriptionが表示されるよう、HugoのAnubisというThemeのfooter.htmlに以下を追加しました2。テーマ側でOGPに対応していなければどのテーマでも自分で書くことになります。

<meta property="og:url" content="{{ .Permalink }}" />
<meta property="og:type" content="{{ if .IsHome }}website{{ else }}article{{ end }}" />
<meta property="og:site_name" content="{{ .Site.Title }}" />
<meta property="og:title" content="{{ .Title }}" />
<meta property="og:description" content="{{ with .Description -}}{{ . }}{{ else -}}{{ if .IsPage }}{{ substr .Summary 0 300 }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}" />
<meta property="og:image" content="{{ if .Params.thumbnail -}}{{ .Params.thumbnail|absURL }}{{ else if and .IsPage (eq .Section "posts")}}{{ path.Join "ogp" (print .Slug ".png") | absURL }}{{ else -}}{{ "img/default.png" | absURL }}{{ end -}}" />

posts/yyyymmdd-hoge-fuga-piyo/index.mdのOGP画像として、ogp/{slug}.pngが読み込まれます。

このため、各記事のindex.mdのslugをhoge-fuga-piyoにする必要があります。ディレクトリ名のyyyymmdd-hoge-fuga-piyohoge-fuga-piyoとslugを一致させる必要があるということです。実装的に微妙な気はしますがこれで困らないのでいいかなと…。

なお、OGP画像の下に展開されるdescriptionは、各index.mdのFront Matterにdescriptionを書いていればそれが、書いていなければFront Matterの直下から最初の300文字になります。

GitHub Actionsのyamlファイルの作成

以上をGitHub Actionsにするとこうなります。.github/workflows直下に拡張子.ymlで任意のファイル名で保存します。

OGP生成に必要なのはSetup GoInstall tcardgenGenerate ogp imagesです。他は通常のHugoのビルド部分とGitHub Pagesへのpush部分なので必要に応じて変更してください。

name: github-pages

on:
  push:
    branches:
      - master

jobs:
  deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    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
        uses: actions/setup-go@v4
        with:
          go-version: '1.21.1'

      - name: Install tcardgen
        run: go install github.com/Ladicle/tcardgen

      - name: Generate ogp images
        run: |
          mkdir -p static/ogp
          files=`find ./content/posts/*/index.md -type f`
          for f in $files; do
              slugname=`dirname ${f} | xargs -I@ basename @ | cut -c 10-`
              tcardgen --fontDir ogp/font --output "static/ogp/${slugname}.png" \
                --template ogp/tcard/template.png ${f}
          done

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

      - name: Build
        run: hugo

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        with:
          deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
          external_repository: suzuna/blog
          publish_dir: ./docs
          publish_branch: main

tcardgenを改造する版

わたしはtcardgenを改造しましたので、上と少し違ったGitHub Actionsを動かしています。

tcardgenは、HugoのyamlのFront MatterのTitleだけでなく、CategoryとTag, Author, DateをOGP画像に含めます。

しかし個人的にCategory, Tag, Author, DateはOGP画像に含めなくていいと思ったのと、Authorの代わりにブログのタイトルを表示したかったので、tcardgenをforkしてソースをいじって実現しました。Goは全く分からないのでソースは汚いです。

本家のインストールのこちらの代わりに、

go install github.com/Ladicle/tcardgen@latest

自作のfork版をインストールします。

go install github.com/suzuna/tcardgen@latest

forkしてGoを書いていじれる人はこんな記事を見なくてもできると思いますが…

自作のfork版を使っているため、GitHub ActionsのInstall tcardgenGenerate ogp imagesはさきほど紹介したものの代わりにこちらを使っています。Generate ogp imagesのtcardgenの実行部分に--topTitle--bottomAuthorというオプションが付いているのは独自仕様です。

- name: Setup Go
  uses: actions/setup-go@v4
  with:
    go-version: '1.21.1'

- name: Install tcardgen
  run: go install github.com/suzuna/tcardgen

- name: Generate ogp images
  run: |
    mkdir -p static/ogp
    files=`find ./content/posts/*/index.md -type f`
    for f in $files; do
        slugname=`dirname ${f} | xargs -I@ basename @ | cut -c 10-`
        tcardgen --fontDir ogp/font --output "static/ogp/${slugname}.png" \
          --template ogp/tcard/template.png --topTitle "" --bottomAuthor "suzuna's memo" ${f}
    done

参考


  1. うずらフォントもですが、BoldやMediumがないフォントの場合は、-Bold, -Mediumをファイル名につけた全く同じフォントを置く必要があります。tcardgenの内部でファイル名をパースしているからです。もちろん、そのようにしてBoldやMediumがないフォントを使うと、OGP画像のフォントの太さは当然すべて同じになります。 ↩︎

  2. footer.htmlはthemes/hugo-theme-anubis/layouts/partials/footer.htmlにあるので、実際には、これをプロジェクトルートディレクトリ直下のlayouts/partials/footer.htmlにコピーし、そのコピーしたファイルに追加しました。theme/<テーマ名>/layouts/以下のファイルをlayouts以下のファイルで上書きできるので、テーマを編集する場合は後者を変更します。そうしないとテーマ本体のアップデートによって編集内容が上書きされてしまいます。 ↩︎