Hugoにブログカードを埋め込む
Netlify FunctionsでJSONを返すAPIを立ち上げたらいい感じにできた話
HugoとAcademicテーマを使ってブログを構築したのはいいのですが、すぐに欲しい機能がないことに気付きました。それは「ブログカード」です。ブログカードというのは、以下のようなURLの埋め込み表示形式のことをいい、これがないとURLの文字列の表示だけになってしまい(または マークダウンで文字列リンクとするだけ)、なんとも寂しい表示になってしまいます。
このブログカードを作る方法についてですが、Hugoのデフォルト機能には入っておらず、基本的には自作するほかありません。また自作するにあたっては、Hugoの機能であるShortcodeを用いて解決することが可能です。
Shortcodeとは
Shortcodeというのは、マークダウンテキスト内に書けるSnippetのことで、ビルド時にSnippetのテンプレートからhtmlを生成して埋め込んでくれます。デフォルトでもTwitterのツイートやinstagramの投稿などいくつかのソーシャルメディアに対応するShortcodeがあります。今回はこのShortcodeを使ってブログカードのSnippetを作ろうという試みです。
仕組み
まず、ブログカードを作るために必要なものとして、metaタグの解釈があります。URLそのものから遷移先のページタイトルや画像などを取得する必要があるためです。幸い、Shortcodeに対応するテンプレートhtmlを作る際、テンプレート内で getJSON
というJsonリクエストをできる関数が用意されているためこれを使えば特定のURLからogpのmetaタグをパースしたJsonを返してそれを用いることができそうです。
URLにアクセスしてタグをパースしてJsonで返却するAPIは、今回はNetlify Functionsを使うことにしました。理由はデプロイにNetlifyを使っており同じサービス・同じアカウントで機能を使った方が楽だと思ったからです。また、利用するのはビルドする時だけで、一度要素が取得できればキャッシュされるということで、無料枠の範囲内で十分事足ります。
実装手順
本記事では、Hugo+AcademicテーマをNetlifyでデプロイしている前提で説明します。また、執筆時点のNetlify Functionsは NodeJS12.x
が対応していますので、NodeJS12
の環境を作っておく必要があります。
Netlify Functions環境のセットアップ
まず、執筆時点でのNetlifyのデフォルト環境では NodeJS10.x
で実行されますので、それを12にするために環境変数を設定します。
Netlifyにログインし、Site dashboardから、Settings > Build & deploy > Environment > Environment variables
の順に進み、以下の環境変数を入れておきます。

今回は、 src/functions/
にFunctionsのソースファイルを置き、 functions/
にビルドしたFunctionsファイルを置く設定にします。まずはファイルだけ作っておきます。
mkdir -p src/functions functions
#名前はhoge.jsとしていますが、任意のもので構いません。
touch src/functions/hoge.js
ルートディレクトリにある netlify.toml
を編集します。以下のようにfunctionsのディレクトリ指定と、hugoコマンドの実行前に後ほど設定する yarn run build
を追加します。
[build]
command = "yarn run build && hugo --gc --minify -b $URL"
publish = "public"
functions = "./functions"
Netlify Functionsのビルドと、Fuctions内でOGPパーサーを利用するために netlify-cli
と netlify-lambda
と open-graph-scraper
をインストールします。
yarn global add netlify-cli
yarn add --dev netlify-lambda
yarn add open-graph-scraper
さらに package.json
に以下のscriptsを追加しておきます。
{
"scripts": {
"build": "netlify-lambda build src/functions/",
"serve": "netlify dev"
}
}
$yarn run build
を実行すると、src/functions/
内のファイルをビルドしてfunctions/
ディレクトリにjsファイルを配置します。$yarn run dev
を実行すると、WebサーバとFunctions双方のローカルサーバが立ち上がります- Webサーバ:
http://localhost:8888
- Functions:
http://localhost:34567/<Function名>
- 例通りにやると、Function名は
hoge.js
です。
- 例通りにやると、Function名は
- Webサーバ:
netlify-lambda serve
という別のコマンドがありますが、legacy commandとされており、現在は上記のやり方が推奨とされています。Functionを作成する
src/functions/hoge.js
を以下のように実装します。
exports.handler = (event, _context, callback) => {
if ('url' in event.queryStringParameters === false) {
console.error("parameter 'url' is necessary!!");
return;
}
const url = event.queryStringParameters.url;
const options = {'url': encodeURI(url)}
const ogs = require("open-graph-scraper");
ogs(options).then(function(data) {
const metadata = data.data;
console.log(metadata);
let ogpData = {};
ogpData['siteName'] = metadata.ogSiteName;
ogpData['title'] = metadata.ogTitle;
ogpData['description'] = metadata.ogDescription;
if (Array.isArray(metadata.ogImage)) {
const jpgUrl = metadata.ogImage.find((image) => image.url.endsWith('.jpg') || image.url.endsWith('.jpeg')).url
ogpData['image'] = jpgUrl
} else {
ogpData['image'] = metadata.ogImage.url;
}
console.log(JSON.stringify(ogpData));
callback(null, {
statusCode: 200,
"headers": { "Content-Type": "application/json; charset=utf-8"},
body: JSON.stringify(ogpData)
});
}).catch(function(error) {
console.error(error);
});
};
event.queryStringParameters.url
でブログカードに利用するURLを取得- open-graph-scraperを使ってURLのmetaタグをパース
- パースしたデータから最低限必要なデータを取得し、Jsonを返す
これで、metaタグを解釈するAPIが完成します。Functionsサーバを立ち上げて、 curl http://localhost:34567/hoge?url=<任意のURL>
とするとJsonデータが取得できるようになります。
Shortcodeを作成する
まずはFunctionのエンドポイントを設定しておきます。 config/_default/config.toml
に設定しておくのがわかりやすいです。ローカルでのテスト時はlocalhostにしてください。
[params]
# OgpApiEndpoint = "http://localhost:34567/.netlify/functions/hoge?url="
OgpApiEndpoint = "https://<netlifyのhost名>/.netlify/functions/hoge?url="
次にacademicテーマ内にある shortcodes
ディレクトリにhtmlを書いていきます。ファイル名がShortcode呼び出し時の名前になります。
{{ $url := .Get 0 }}
{{ $jsonData := getJSON $.Page.Site.Params.OgpApiEndpoint $url }}
{{ $siteName := $jsonData.siteName }}
{{ $title := $jsonData.title }}
{{ $description := $jsonData.description }}
{{ $image := $jsonData.image }}
{{ $urlInfo := urls.Parse $url }}
{{ $host := printf "%s" $urlInfo.Host }}
{{ $prefix := "https://www.google.com/s2/favicons?domain=" }}
{{ $favicon := printf "%s%s" $prefix $urlInfo.Host }}
<div class="box">
<a class="box-content" target="_blank" rel="noopener" href="{{ $url }}">
<div class="box-content">
<div class="box-left">
<h2 class="box-title">{{ $title }}</h2>
<div class="box-description">{{ $description }}</div>
<div class="box-host">{{ $host }}</div>
</div>
<div class="box-image-container" style="background-image: url('{{ $image }}');" ></div>
</div>
</a>
</div>
対応するCSS(SCSS)はこんな感じ。
div.box {
margin-top: 16px;
margin-bottom: 16px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1), inset 0 0 0 1px rgba(0, 0, 0, 0.1);
}
.dark div.box {
box-shadow: 0 1px 4px rgba(255, 255, 255, 0.1), inset 0 0 0 1px rgba(255, 255, 255, 0.1);
}
a.box-content {
-webkit-tap-highlight-color: transparent;
text-decoration: none;
color: rgba(0, 0, 0, 0.8);
}
.dark a.box-content {
color: rgba(255, 255, 255, 0.8);
}
a.box-content.hover {
text-decoration: none;
}
.dark h2.box-title {
color: rgba(255, 255, 255, 1);
}
div.box-content {
display: flex;
align-items: center;
}
div.box-left {
flex: 1;
padding: 16px 20px;
}
h2.box-title {
font-size: 18px;
font-weight: 600;
line-height: 20px;
margin: 0;
}
div.box-description {
font-size: 16px;
line-height: 20px;
margin-top: 8px;
}
div.box-host {
margin-top: 12px;
font-size: 15px;
color: rgba(0, 0, 0, 0.54);
line-height: 20px;
margin-top: 12px;
}
.dark div.box-host {
color: rgba(255, 255, 255, 0.2);
}
div.box-image-container {
width: 160px;
height: 167px;
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
}
Shortcodeを利用する
ここまでできたら後は呼び出すだけです。新しく記事を新規作成して、その中で上のshortcodeを呼び出します。仮にファイル名が blogcard.html
とすると{ {% blogcard "<URL>" %}}
のように書くことで呼び出しができます({}のスペースは無くしてください)。
という感じになります。そしてファイルを保存しビルドするときにHugoサーバがFunctionosをリクエストしてJsonを取得し、ブログカードの表示に置き換われば完成です。
デプロイする
ここまでできたら、コミットしてプッシュします。Netlifyのデプロイタスクが自動的に実行され、Functionsの設定まで行われるので、特別にやることはありません。
終わりに
気になっていることとして、URLによっては望まない画像が表示されてしまうことがあります。これはまた今後Functionsに手を入れてなおしていきたいと思います。
下記のサイトが非常にわかりやすく、参考になりました。