HugoでOGPとXのカード設定を行う

はじめに

zenn.devqiitaでは、X(旧:Twitter)にリンクを貼ると、いい感じのプレビュー画像が出てくる。
あれを自分のブログでも設定したいなと思ったので、設定した際の備忘録である。

環境

Hugo 0.111.3-ext
Jane

Docker 24.0.6
Docker Composer v2.22.0-desktop.2

前知識

どのようにすれば設定できるかについて調べてみたところ、どうやら OGPを設定する必要があるらしい。
The Open Graph protocol をみてみたところ、 metaタグに og:title, og:imageなどの要素を入れてあげることで設定できるようだ。
また、X(旧:Twitter)では独自のタグがあるようで、ツイートをカードで最適化する-X開発者プラットフォームを見ると、この辺りの設定も必要なようだ。

検討

実現するにあたっていくつか検討事項があった。

  • OGP画像はどのように用意するのか?
  • Hugoでmetaタグの設定ができるのか?

1点目のOGP画像の用意についてだが、シンプルで良いので1記事ごとに自分で作るのは正直かなりしんどい。
なので、自動生成をする方法を考えた。
Imagamagickでテンプレート読み込ませて生成させればいけそうだが、一から自分で作成するのは大変そうだなと思ったので、他の人はどうしているのかをみてみた。

そこで、https://zenn.dev/catnose99/scraps/895b398991918f を見つけ、この中にある事例を参考にしてみたところ、Ladicle/tcardgen | GitHub が良さそうだった。
これであれば、 テンプレート画像を用意するだけであとは自動で生成してくれるのでこれを使用させていただくことにした。

2点目の metaタグの設定についてだが、これは以前 GoogleAnalyticsを設定した際(GoogleAnalyticsをHugo Blogに導入する)に、layouts/partials/custom_head.htmlを作成して、その中にAnalyticsの設定を書いたなというのがあったので問題なさそうだ。

OGP用の画像の自動生成について

Ladicle/tcardgen | GitHubを見ると、テンプレート画像を用意して、そのあとコマンド実行という流れなのでその通りに実行する。

テンプレート画像の作成

Figmaで適当に作成した。
card

これを、 static/images/card.pngとして保存しておいた。

Dockerイメージの作成

Ladicle/tcardgen | GitHubを使用するにあたって、ローカル環境で動作させる必要がある。
そのため、Hugoで使用している docker-composeの構成に、追加でtcardgenを使用できるイメージを作成する必要があった。
というわけなので、以下のファイルに修正を適用した。

compose.yml

# https://gohugo.io/installation/linux/#docker
services:
  hugo:
    image: klakegg/hugo:0.111.3-ext-ubuntu
    entrypoint: tail -f /dev/null
    volumes:
      - .:/src
    ports:
      - "1313:1313"
+ tcardgen:
+   build: 
+     context: ./Docker/tcardgen
+   volumes:
+     - .:/blog

./Docker/tcardgen

FROM golang:1.21.6-bookworm

RUN mkdir -p /blog

# FYI: https://github.com/Ladicle/tcardgen
RUN go install github.com/Ladicle/tcardgen@latest
RUN git clone https://github.com/Ladicle/tcardgen.git /tmp/tcardgen

RUN apt-get install -y git
RUN git clone https://github.com/ookamiinc/kinto.git /tmp/kinto

CMD ["tail", "-f", "/dev/null"]

Makefileに下記を追加

tcardgen:
    make up
    docker compose exec tcardgen bash

tcardgen-generate:
    make up
    docker compose exec tcardgen sh -c "\
    find /blog/content -name "*.md" -type f | \
    xargs tcardgen -f /tmp/kinto/'Kinto Sans' \
    -o /blog/static/tcard \
    -t /blog/static/images/card.png"

上記の使い方としては、make tcardgen-generateをすることで、static/tcard/いかにイメージが生成される。

内容の解説としては、Makefile内の tcardgen-generateのコマンドとして、 find /blog/content -name "*.md" -type f で記事内のコンテンツ(*.md)を検索し、
それをパイプで xargstcardgenコマンドに渡す。
tcardgenのオプションとして、-fKinto Sansフォントを指定し、 -o(アウトプット)で、/blog/static/tcardを指定する。
ちなみに、 /blog/static/tcardは、ホストとバインドマウントしており、 ホスト側のstatic/tcard/になる。
-tで テンプレートを指定する (/blog/static/images/card.png)。これは先ほど作成した画像を指定している。

コマンド実行

make tcardgen-generate
make up
docker compose up -d
[+] Building 0.0s (0/0)                                                                                                  docker:desktop-linux
[+] Running 2/0
 ✔ Container blog-hugo-hugo-1      Running                                                                                               0.0s 
 ✔ Container blog-hugo-tcardgen-1  Running                                                                                               0.0s 
docker compose exec tcardgen sh -c "\
                find /blog/content -name "*.md" -type f | \
                xargs tcardgen -f /tmp/kinto/'Kinto Sans' \
                -o /blog/static/tcard \
                -t /blog/static/images/card.png"
Load fonts from "/tmp/kinto/Kinto Sans"
Load template from "/blog/static/images/card.png" directory
Success to generate twitter card into /blog/static/tcard/senior-thesis.png
Failed to generate twitter card for /blog/static/tcard/recentry-2020-02-3w.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/recentry-2020-02-2w.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/recentry-2020-04-4w.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/recentry-2020-02-1w.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/recentry-2020-02-5w.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/recentry-2020-02-4w.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/recentry-2020-04-1w.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/recentry-2020-01-2w.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/recentry-2020-01-3w.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/recentry-2020-03-1w.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/recentry-2020-03-3w.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/recentry-2020-03-2w.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/recentry-2020-01-4w.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/introduction.png: "tags" is not defined or empty
Success to generate twitter card into /blog/static/tcard/book-2024-01.png
Failed to generate twitter card for /blog/static/tcard/recentry-2020-03-4w.png: "tags" is not defined or empty
Success to generate twitter card into /blog/static/tcard/pass-fp2.png
Success to generate twitter card into /blog/static/tcard/pass-bookkeeping3.png
Failed to generate twitter card for /blog/static/tcard/resolution-2023y.png: "tags" is not defined or empty
Success to generate twitter card into /blog/static/tcard/buy-benq-mobiuz-ex2510s.png
Success to generate twitter card into /blog/static/tcard/pass-db.png
Success to generate twitter card into /blog/static/tcard/pass-fp3.png
Success to generate twitter card into /blog/static/tcard/review-2023y.png
Success to generate twitter card into /blog/static/tcard/resolution-2024y.png
Success to generate twitter card into /blog/static/tcard/pass-nw.png
Failed to generate twitter card for /blog/static/tcard/recentry-2022-07-4w.png: "tags" is not defined or empty
Success to generate twitter card into /blog/static/tcard/client-character-set.png
Success to generate twitter card into /blog/static/tcard/mysql-replication.png
Success to generate twitter card into /blog/static/tcard/compare-tables-with-checksum-statement.png
Success to generate twitter card into /blog/static/tcard/mysql-case-sensitive.png
Success to generate twitter card into /blog/static/tcard/use-mysql-tuner.png
Success to generate twitter card into /blog/static/tcard/mysql-create-history-table-with-trigger.png
Success to generate twitter card into /blog/static/tcard/use-sql-with.png
Failed to generate twitter card for /blog/static/tcard/login-form-save-password-dialog.png: "categories" is not defined or empty
Success to generate twitter card into /blog/static/tcard/setting-ogp-and-x-card-for-hugo.png
Success to generate twitter card into /blog/static/tcard/web-develop-watch-list.png
Success to generate twitter card into /blog/static/tcard/play-2048.png
Failed to generate twitter card for /blog/static/tcard/valheim-migrate-from-local-to-ec2-server.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/valheim-server-ec2-arm-instance.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/docker-lamp.png: "tags" is not defined or empty
Success to generate twitter card into /blog/static/tcard/ssh-2fa-for-docker.png
Success to generate twitter card into /blog/static/tcard/docker-autoheal.png
Success to generate twitter card into /blog/static/tcard/vue2-to-vue3.png
Success to generate twitter card into /blog/static/tcard/failed-to-find-a-valid-digest.png
Success to generate twitter card into /blog/static/tcard/eol-notify-python.png
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-18.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-28.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/go-competitive-programming-code-01.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-29.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-19.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-1.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-26.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-12.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-5.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-16.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-22.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-4.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-17.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-23.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-27.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-13.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-7.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-14.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-20.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-3.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-24.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-10.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-2.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-25.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-11.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-6.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-15.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-21.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-9.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/build-web-application-with-golang-8.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/go-competitive-programming-code-02.png: "tags" is not defined or empty
Success to generate twitter card into /blog/static/tcard/convert-html-to-pdf-with-headless-chrome.png
Success to generate twitter card into /blog/static/tcard/refactor-vue3-composition-api.png
Success to generate twitter card into /blog/static/tcard/use-chatgpt-api.png
Success to generate twitter card into /blog/static/tcard/renew-article.png
Failed to generate twitter card for /blog/static/tcard/ai-kiritan-play.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/building-gitlab-on-ec2.png: "author" is not defined or empty
Success to generate twitter card into /blog/static/tcard/use-elemental-media-convert.png
Success to generate twitter card into /blog/static/tcard/aws-backup-success-job-notifications.png
Success to generate twitter card into /blog/static/tcard/send-mail-ec2-with-postfix-use-aws-ses.png
Success to generate twitter card into /blog/static/tcard/remove-exif.png
Success to generate twitter card into /blog/static/tcard/use-figure-code-jane.png
Success to generate twitter card into /blog/static/tcard/use-markdown-vscode-extension.png
Success to generate twitter card into /blog/static/tcard/use-exiftool.png
Success to generate twitter card into /blog/static/tcard/use-github-connect-with-ssh-over-https.png
Success to generate twitter card into /blog/static/tcard/diff-two-files-with-vscode.png
Success to generate twitter card into /blog/static/tcard/what-i-think-about-when-migrating-php-versions-to-vanilla-php.png
Success to generate twitter card into /blog/static/tcard/use-sl.png
Success to generate twitter card into /blog/static/tcard/use-countdown.png
Success to generate twitter card into /blog/static/tcard/use-pdfcrack.png
Success to generate twitter card into /blog/static/tcard/reduce-volume-ebs.png
Success to generate twitter card into /blog/static/tcard/git-attention.png
Success to generate twitter card into /blog/static/tcard/use-flock.png
Success to generate twitter card into /blog/static/tcard/apache-tomcat-configuration-to-let-apache-handle-static-resources.png
Success to generate twitter card into /blog/static/tcard/convert-heic-to-png-with-imagemagick.png
Success to generate twitter card into /blog/static/tcard/use-qrencode.png
Success to generate twitter card into /blog/static/tcard/use-ntpdate.png
Success to generate twitter card into /blog/static/tcard/use-apvlv.png
Success to generate twitter card into /blog/static/tcard/mount-wasabi-bucket-with-aws-cli.png
Success to generate twitter card into /blog/static/tcard/use-watch.png
Success to generate twitter card into /blog/static/tcard/ec2-attach-volume.png
Success to generate twitter card into /blog/static/tcard/install-lightbox2-to-hugo-blog.png
Success to generate twitter card into /blog/static/tcard/use-csvtomd.png
Success to generate twitter card into /blog/static/tcard/use-bash-shortcut.png
Success to generate twitter card into /blog/static/tcard/use-slack-notifications-gitlab.png
Success to generate twitter card into /blog/static/tcard/use-yum-whatprovides-option.png
Success to generate twitter card into /blog/static/tcard/convert-html-to-pdf.png
Success to generate twitter card into /blog/static/tcard/nginx-before-tls-1_1-disable.png
Success to generate twitter card into /blog/static/tcard/use-xargs.png
Success to generate twitter card into /blog/static/tcard/use-apg.png
Success to generate twitter card into /blog/static/tcard/git-mv.png
Success to generate twitter card into /blog/static/tcard/rescue-ec2-instance.png
Success to generate twitter card into /blog/static/tcard/use-pdftocairo.png
Success to generate twitter card into /blog/static/tcard/use-webp.png
Success to generate twitter card into /blog/static/tcard/use-zcat.png
Success to generate twitter card into /blog/static/tcard/convert-excel-to-pdf.png
Success to generate twitter card into /blog/static/tcard/ec2-eni-take-over-ip.png
Success to generate twitter card into /blog/static/tcard/create-certificates-with-san.png
Success to generate twitter card into /blog/static/tcard/use-fcrackzip.png
Success to generate twitter card into /blog/static/tcard/use-aamath.png
Success to generate twitter card into /blog/static/tcard/use-rlogin-portforward.png
Success to generate twitter card into /blog/static/tcard/install-gitlab-update-script-amzn-2023-to-2023.png
Success to generate twitter card into /blog/static/tcard/use-codequery.png
Success to generate twitter card into /blog/static/tcard/lost-aws-ec2-keypair.png
Success to generate twitter card into /blog/static/tcard/use-barcode.png
Success to generate twitter card into /blog/static/tcard/use-circumflex.png
Success to generate twitter card into /blog/static/tcard/list-files-modified-after-the-specify-datetime.png
Success to generate twitter card into /blog/static/tcard/use-pdfunite.png
Success to generate twitter card into /blog/static/tcard/use-logrotate.png
Success to generate twitter card into /blog/static/tcard/use-busybox.png
Success to generate twitter card into /blog/static/tcard/use-calc.png
Success to generate twitter card into /blog/static/tcard/fix-code-quote-shaping.png
Success to generate twitter card into /blog/static/tcard/use-cbm.png
Success to generate twitter card into /blog/static/tcard/use-ansiweather.png
Success to generate twitter card into /blog/static/tcard/use-tesseract.png
Success to generate twitter card into /blog/static/tcard/use-pdfseparate.png
Success to generate twitter card into /blog/static/tcard/use-jq.png
Success to generate twitter card into /blog/static/tcard/use-csview.png
Success to generate twitter card into /blog/static/tcard/use-colordiff.png
Success to generate twitter card into /blog/static/tcard/update-hugo-version-from-0.110-to-0.111.png
Success to generate twitter card into /blog/static/tcard/use-md5-sum.png
Success to generate twitter card into /blog/static/tcard/construct-gitlab-runner-docker.png
Success to generate twitter card into /blog/static/tcard/gitlab-mr-review-with-chatgpt-2.png
Success to generate twitter card into /blog/static/tcard/gitlab-mr-review-with-chatgpt.png
Success to generate twitter card into /blog/static/tcard/construct-hugo-docker.png
Failed to generate twitter card for /blog/static/tcard/vscode-php.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/vscode-nuxt.png: "tags" is not defined or empty
Success to generate twitter card into /blog/static/tcard/upgrade-gitlab-15-to-16.png
Success to generate twitter card into /blog/static/tcard/docker-init.png
Success to generate twitter card into /blog/static/tcard/construct-eol-apache-tomcat.png
Success to generate twitter card into /blog/static/tcard/construct-gitlab-ce-docker.png
Failed to generate twitter card for /blog/static/tcard/using-cvs2git.png: "tags" is not defined or empty
Success to generate twitter card into /blog/static/tcard/construct-apache-tomcat-mysql-to-amazonlinux2023-with-ansible.png
Failed to generate twitter card for /blog/static/tcard/migrate-from-s3-to-wasabi.png: "tags" is not defined or empty
Success to generate twitter card into /blog/static/tcard/use-cloudflare-ddns.png
Success to generate twitter card into /blog/static/tcard/construct-gitlab-ce-with-ansible.png
Success to generate twitter card into /blog/static/tcard/construct-ec2-public-private-subnet-alb.png
Success to generate twitter card into /blog/static/tcard/use-ansible-the-first-time.png
Success to generate twitter card into /blog/static/tcard/deploy-portfolio-to-vercel.png
Success to generate twitter card into /blog/static/tcard/construct-ec2-simple-architecture-with-terraform.png
Failed to generate twitter card for /blog/static/tcard/migrate-from-route53-to-cloudflare.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/emulate-raspbian-os-with-qemu.png: "categories" is not defined or empty
Success to generate twitter card into /blog/static/tcard/introduction-google-analytics-for-hugo.png
Failed to generate twitter card for /blog/static/tcard/hugo-theme-update.png: "tags" is not defined or empty
Failed to generate twitter card for /blog/static/tcard/about.png: "author" is not defined or empty
2024/01/23 05:53:55 failed to generate 62 twitter cards
make: *** [tcardgen-generate] Error 123

authortagsがないものは自動生成されない。
昔作成した記事はこんな感じなので、面倒なのでそのままにする。

例えば別の記事のサマリー画像は以下になる。
use-zcat

Hugoのcustom_head.htmlの修正

先ほども記載したが、custom_head.htmlを修正することで、設定できるので修正を行う。
ただ、実際にやってみると、二重に設定されてしまうようなので、自分の使用しているテーマJaneの実装を確認した。

layouts/partials/head.htmlをみてみると、

{{/* NOTE: These Hugo Internal Templates can be found starting at https://github.com/spf13/hugo/blob/master/tpl/tplimpl/template_embedded.go#L158 */}}
{{- template "_internal/opengraph.html" . -}}
{{- template "_internal/schema.html" . -}}
{{- template "_internal/twitter_cards.html" . -}}

となっているので、 _internalopengraph.html, twitter_cards.htmlにOGP画像のmetaタグが入っているようだった。

なので、layouts/_internalを作成し、opengraph.htmlと、twitter_cards.htmlを用意し、metaタグを記載した。

<!-- General -->
<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 .File (hasPrefix .File.Path "post") -}}{{ path.Join "tcard" (print .File.BaseFileName ".png") | absURL }}{{ else -}}{{ "img/default.png" | absURL }}{{ end -}}" />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@{{ .Site.Params.twitterName }}" />

ここまでで設定が完了したので、ソースを表示して確認しておこう。
これら成果物を masterpushする。

プレビュー確認

masterpush後に netlifyでのデプロイが走るので、下記のサイトで確認をする。

web-toolbox

今後の改善

  • GitHub Actions を使って成果物を特定のディレクトリにコミットしたい。

参考

おわりに

今まで気になっていたOGP画像の設定ができたので満足。
tcardgenには感謝です。

Hugo で構築されています。
テーマ StackJimmy によって設計されています。