
Gatsby.js × Strapi × NetlifyのJamstack構築でビルド時間のタイムアップエラーを回避する
はじめに
今日は久々に「Web」カテゴリのブログです。ごりごり技術コンテンツですみません。
唐突ですがみなさん、ヘッドレスCMSのStrapi使ってますか?CMS自体のコードを直接いじれたり、DBも自前で用意するので(とはいえサーバーレスでok)、少し手間はかかる分自由度が高くて便利です。
Strapiでコンテンツ更新のたびにサイトを自動でビルド・デプロイしたいので、最初は、StrapiのwebhookをNetlifyにつないで自動化してました。ところが、コンテンツが増えすぎてNetlifyのビルド制限に引っかかってしまい...。なんとかビルド時間を減らそうとしたのですが、単にコンテンツの数の暴力で歯がたちませんでした。
そこで、Netlifyでビルドするのではなく、GitHub Actionsでなんとかしようと思ったのですが、これがまた問題だらけでして。
問題点
StrapiのwebhookをGitHub Actionsのトリガーにしようとしたら、こんな壁にぶち当たりました:
- Strapiのwebhookって、リクエストBodyが空っぽなんです。というか設定ができない。
- でも、GitHub Actionsの
repository_dispatch
イベントを使うには、Bodyにevent_type
を入れないといけない。
つまり、Strapiの標準webhookじゃGitHub Actionsを呼べないんですよ、、
解決策:Strapiにミドルウェアを追加しよう
ってことで、Strapiにカスタムミドルウェアを追加することにしました。この方法なら、Strapiのコア機能を直接変更せずに済みます。具体的にはこんな感じです:
- Strapiのsrcディレクトリに新しいミドルウェアファイルを作る
- このミドルウェアで、コンテンツが変わったらGitHub Actionsを呼び出す仕組みを実装する
- Strapiの設定ファイルに新しいミドルウェアを追加する
実装手順
- まず、
/src/middlewares/triggerGithubActions.js
を作成し、以下のように書きます:
const fetch = require("node-fetch");
let debounceTimer = null;
const DEBOUNCE_DELAY = 5 * 60 * 1000;
async function triggerGithubActions() {
const githubToken = process.env.GITHUB_TOKEN;
const owner = process.env.GITHUB_OWNER;
const repo = process.env.GITHUB_REPO;
if (!githubToken) {
console.error("GitHub token is not set");
return;
}
if (!owner) {
console.error("GitHub owner is not set");
return;
}
if (!repo) {
console.error("GitHub repo is not set");
return;
}
try {
const response = await fetch(
`https://api.github.com/repos/${owner}/${repo}/dispatches`,
{
method: "POST",
headers: {
Authorization: `Bearer ${githubToken}`,
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
"Content-Type": "application/json",
},
body: JSON.stringify({
event_type: "webhook",
}),
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
console.log("GitHub Actions triggered successfully");
} catch (error) {
console.error("Error triggering GitHub Actions:", error);
}
}
function debouncedTrigger() {
if (debounceTimer) {
clearTimeout(debounceTimer);
}
debounceTimer = setTimeout(() => {
triggerGithubActions();
debounceTimer = null;
}, DEBOUNCE_DELAY);
}
module.exports = () => {
return async (ctx, next) => {
await next();
const { method, url } = ctx.request;
if (
["POST", "PUT", "DELETE", "PATCH"].includes(method) &&
!url.startsWith("/upload")
) {
debouncedTrigger();
}
};
};
- 次に、
/config/middlewares.js
に以下のように追記します:
module.exports = [
// ... 他のミドルウェア設定 ...
{
name: "global::triggerGithubActions",
config: {},
},
];
動作の仕組み
- Strapiでコンテンツが変更されると(POST, PUT, DELETE, PATCHリクエスト)、ミドルウェアが起動します。
- ミドルウェアは変更を検知し、一定時間(5分)の間に複数の変更があった場合はそれらをまとめて1回のアクションとして扱います(デバウンス処理)。今回のプロジェクトはStrapiのAPIを用いて複数のコンテンツを一度にPOSTしていたので、都度ビルドしてしまうのを防ぐためです。
- デバウンス処理後、GitHub APIを使って起動の合図を送ります。
- その合図で、設定しておいたGitHub Actionsのワークフローが動き出します。
注意点
- 環境変数(
GITHUB_TOKEN
,GITHUB_OWNER
,GITHUB_REPO
)を適切に設定する必要があります。 - GitHub Personal Access Tokenを使用するので、セキュリティには十分注意してください。
- このミドルウェアは全てのコンテンツタイプに対して動作します。特定のコンテンツタイプのみに制限したい場合は、さらにロジックを追加する必要があります。
まとめ
この方法で、Strapiのコンテンツ更新をきっかけにGitHub Actionsを走らせられるようになりました。Netlifyの制限?もう知りません。柔軟なビルドとデプロイのワークフローが手に入りましたよ。
みなさんも、ヘッドレスCMSとCI/CDの連携で困ったら、このミドルウェアの追加を試してみてください!