HugoでAMP対応のブログカードを作る

「Hugoでもブログカードを利用したい」

そう考えているところに以下の記事がTwitterのTLで流れてきたので、試してみました。

Hugoでブログカードに対応する | Hugo 入門 / 解説 | nasust dev blog

ブログカードのShortcodeと表示例

{{% blogcard https://nasust.com/hugo/shortcode/blogcard/ %}}

blogcardというShortcodeを利用することで、対象ページのTitle, Description, OGPを取得して表示します。

Shortcodeblogcardの実装は以下の通りです。ローカルに立ち上げたAPIサーバ(Express)に対してgetJSONを行うことで、ビルド時に情報を取得します。

Shortcode for blogcard

{{ $url := ( .Get 0) }}
{{ $getURL := printf "http://localhost:6060/getogp?%s" (querify "url" $url ) }}
{{ $ogpjson := getJSON $getURL }}
{{ $getSizeURL := printf "http://localhost:6060/size?%s" (querify "url" $ogpjson.image ) }}
{{ $imgjson := getJSON $getSizeURL }}

<p></p><!-- Avoid overriding list -->
<div class="embed">
    <div class="rf">
        <div class="rr">
            <div class="rcx9 rcm9 rpls">
                <div class="rf">
                    <div class="rr">
                        <div class="rcx12 body">
                            <a class="utdn"
                                href="{{ $ogpjson.url }}"><strong>{{ $ogpjson.title }}</strong></a>
                        </div>
                        <div class="rcm12" style="font-size: .8rem;">
                            {{ $ogpjson.description | truncate 50 }}</small>
                        </div>
                    </div>
                </div>
            </div>
            <div class="rcx3 rcm3 rtxc rpxs rpls">
                <a href="{{ $ogpjson.url }}">
                    <div>
                        <amp-img class="" src="{{ $ogpjson.image }}"
                            alt="{{ $ogpjson.title }}" width="75" height="75" layout="fixed"></amp-img>
                    </div>
                </a>
            </div>
        </div>
    </div>
</div>

APIサーバ (app.ts)

上記記事を参考にさせていただきました。/sizeはサイズ取得が必要となったとき用にAPIを生やしました。

Hugoでブログカードに対応する | Hugo 入門 / 解説 | nasust dev blog

import * as express from 'express';
import * as client from 'cheerio-httpcli';
import * as requestImageSize from 'request-image-size';

const app = express();

app.get("/size", (expressRequest, expressResponse, expressNext) => {
    const url = expressRequest.query.url;
    client.fetch(url, (err, $, res, body) => {
        if (err) {
            expressNext(err)
            return;
        }

        requestImageSize(url)
            .then(size => {
                const result = {
                    width: size.width,
                    height: size.height,
                };
                expressResponse.json(result);
            })
            .catch(err => console.error(err));
    });
})

app.get("/getogp", (expressRequest, expressResponse, expressNext) => {
    const url = expressRequest.query.url;
    client.fetch(url, (err, $, res, body) => {
        if (err) {
            expressNext(err)
            return;
        }

        const result = {
            exists: false,
            title: "",
            description: "",
            url: "",
            image: "",
            site_name: "",
            type: "",
        }

        const ogTitleQuery = $("meta[property='og:title']");

        if (ogTitleQuery.length > 0) {
            result.exists = true;
            result.title = $("meta[property='og:title']").attr("content");
            result.description = $("meta[property='og:description']").attr("content");
            result.url = $("meta[property='og:url']").attr("content");
            result.image = $("meta[property='og:image']").attr("content");
            result.site_name = $("meta[property='og:site_name']").attr("content");
            result.type = $("meta[property='og:type']").attr("content");
        } else {
            result.title = $("head title").text()
            result.description = $("meta[name='description']").attr("content");
        }

        expressResponse.json(result);
    });

})

app.listen(6060, () => console.log('Listening on port 6060'));

CI(Azure Pipelines)での設定

CI上でも実行できるように事前にJSON API Serverを実行するようにしました。

- script: |
    npx ts-node src/app.ts &
  displayName: 'Run json api server'

- script: |
    hugo
  displayName: 'Build content by hugo'

まとめ

今まで、HugoのgetJSONを使うときには、外部にAPIサーバを立てないといけないという固定観念がありました。 しかし、よく考えればローカルAPIサーバでも問題なく動作するので、使い方に幅が出そうです。

ただし、「動的にコンテンツを生成するのはHugoの哲学に沿っているのか」という点は少し検討すべきです。 ビルドが超高速なため、編集・確認までをシームレスに行えるのがHugoの魅力です。 getJSONを利用することでビルド速度が落ちてしまったら、魅力が半減してしまうことを懸念しています。 (getJSONはキャッシュを行うので一度情報取得してしまえば問題ない可能性もあります)

しばらくは、試しながら様子を見てみます。

Backlinks

Hugoでブログカードを作成する(resources.GetRemote利用)
以前、 HugoでAMP対応のブログカードを作るでAPIサーバを利用したブログカードの作成方法を紹介した。このときは、getJSONを利用していた。 Hugo v0.91.0でresources.GetRemoteが実装され、getJSONやgetCSV以外にもresources.GetRemoteを利用できるようになった。 Release v0.91.0 · gohugoio/hugo resources.GetRemoteを利用したブログカードの作り方のメモ。 目次 resources.GetRemoteを利用したブログカードの作り方 markdownファイル 参考
2022-08-29
npm-run-allでローカルAPI serverとHugo serverを同時に実行する
このブログではローカルでAPIサーバ(Express)を動かし、HugoのShortcodesから利用しています(2020/03/13現在)。 HugoでAMP対応のブログカードを作る そのため、記事を書いてHugoのプレビューを利用する場合は、2つのターミナルで以下のコマンドを実行していましたが、ちょっとだけ面倒です。 API Server: npx ts-node src/app.ts Hugo Server: hugo server -D そこでnpm-run-allというライブラリを利用して、1つのターミナルで簡単にできるようにしました。
2020-03-13
静的サイトジェネレータHugo
Hugo入門 静的サイトジェネレータ「Hugo」でシンプルブログサイトを構築する 静的サイトジェネレータ「Hugo」インストール 静的サイトジェネレータ「Hugo」〜公開方法〜 サイト構築 Hugoのテーマでsubmoduleを使う方法 HugoでのシンタックスハイライトにPython Pygmentsが不要となった HugoのRelated Contentを利用して関連記事を表示する gulpで画像の最適化 Hugoソースコードリーディング〜Taxonomy〜 Render Hooks for Code Blocksを利用してコードブロックにファイル名を表示する Hugoでブログカードを作成する(resources.GetRemote利用) Templates 【Hugo】Partial Templateでは複数returnを記述する早期Returnを使えない 【Hugo】images.TextでOGP画像を生成する Shortcodes Hugo Shortcodesの作り方 HugoのShortcodesを利用してAmazon紹介リンクタグを作成 HugoでAMP対応のブログカードを作る AMP対応 AMP向けのミニマルCSSフレームワーク「1BX」をHugoに導入した AMPページの最適化〜ぼくのAMPサイトがこんなに遅い訳がない〜 AMP OptimizerによるAMPのさらなる最適化 AMP Service WorkerでPrefetch Linksを実現する Data Driven Content Hugoで人気記事を表示するためJSONを返すAPIサーバを作りData-driven Contentを試してみた Tailwind CSS HugoでTailwindCSSを利用しAMP Validなページを生成する ビルド npm-run-allでローカルAPI serverとHugo serverを同時に実行する GitHub Actionsのスケジューラ実行を利用して定期的にビルドする Circle CIでHugoのビルド・デプロイを実行する コンテンツ作成 Git pre-commitフックでFrontmatterの「更新日時」を自動更新する Hugoでブログ記事一覧ページ(ブログアーカイブページ)を作成する 移行 はてなダイアリーからはてなブログ経由で独自ドメインのブログに記事を移行しました JekyllからHugoへの移行ポイント Hugoで生成した静的サイトのホスト先をさくらVPSからNetlifyに変更する 書籍
2017-12-31