飽き性の頭の中
Gatsby.jsでリンクをリッチな見た目にするのサムネイル

Gatsby.jsでリンクをリッチな見た目にする

2023-09-19に公開

Gatsby.jsを使ってこのブログは作られている。デフォルトでは、一般的なブログサービスでできるようなリッチなリンクは生成できないので、テキストにしていた。

しかし、ようやくリッチなリンクを生成できるようにしたので、その方法を記録しておく。

この記事は、作業中にZennのスクラップに書いていたものをまとめたものである。

https://zenn.dev/tawachan/scraps/9793fc5abda9ed
https://zenn.dev/tawachan/scraps/9793fc5abda9ed

実装方針の確認

具体的な実装方法をまとめる前に、どういう方針で実装したかを整理しておく。

前提条件

まず、前提条件として、次のようなことを想定している。

  • gatsby-source-filesystemを使ってローカルにブログ記事に相当するMarkdownファイルを配置している

実装方針

実装方針としては、次のようなものを想定している。

  1. Markdownファイルを全件調べて、リッチなリンクにする記法1を探し、該当URLをすべて抽出する
  2. そのURLにリクエストし、必要な情報をすべてJSONにまとめ、静的ファイルとして配信する
  3. 該当記法を独自のコンポーネントに置き換えるようにし、そのコンポーネントでJSONを読み込んでリッチな見た目を表示する

既存の記事だと、Gatsbyのビルド時に組み込んでるものが見受けられたが、自分にとっては複雑そうだったので、JSONを配信して、クライアントサイドでレンダリングする方針にした。

調査時に参考にさせてもらった記事

実装の手順

ここからは実際の実装の手順を記録していく。

OGPのデータの取得

ローカルのスクリプトとして、OGPのデータを取得するスクリプトを作成する。これを適宜実行して生成されたJSONファイルをコミットしておく想定。

最終的なスクリプトはこちら。静的ファイルとして配信するために、./static/ogp-data.jsonとしてファイルを出力している。

const fs = require("fs");
const axios = require("axios");
const cheerio = require("cheerio");
const getExistingData = () => {
const existingData = JSON.parse(
fs.readFileSync("./static/ogp-data.json", "utf8")
);
return existingData;
};
const listFiles = (dir) =>
fs
.readdirSync(dir, { withFileTypes: true })
.flatMap((dirent) =>
dirent.isFile()
? [`${dir}/${dirent.name}`]
: listFiles(`${dir}/${dirent.name}`)
);
const extractUrls = (fileTexts) => {
const allUrls = fileTexts.flatMap((fileText) => {
const lines = fileText.split("\n");
const urls = lines
.filter((line) => line.includes("<rich-link"))
.map((line) => {
const $ = cheerio.load(line);
return $("rich-link").attr("href");
})
.filter((url) => url !== undefined);
return urls;
});
const uniqueUrls = Array.from(new Set(allUrls));
return uniqueUrls;
};
const getBlogTexts = (blogPosts) =>
blogPosts
.map((path) => {
const file = fs.readFileSync(path, "utf8");
return file;
})
.filter((text) => text !== "");
const fetchOgpData = async (url) => {
try {
const res = await axios.get(url); // axiosを使ってリクエスト
const $ = cheerio.load(res.data); // 結果をcheerioでパース
const getMetaContent = (property, name) => {
return (
$(`meta[property='${property}']`).attr("content") ||
$(`meta[name='${name}']`).attr("content") ||
""
);
};
const data = {
originalUrl: url,
url: getMetaContent("og:url", "") || res.request.res.responseUrl || url,
domain: new URL(url).hostname,
title: getMetaContent(