Illust

Web

News

About

Contact

Gatsby.js × Strapi × NetlifyのJamstack構築でビルド時間のタイムアップエラーを回避する

Gatsby.js × Strapi × NetlifyのJamstack構築でビルド時間のタイムアップエラーを回避する

はじめに

今日は久々に「Web」カテゴリのブログです。ごりごり技術コンテンツですみません。
唐突ですがみなさん、ヘッドレスCMSのStrapi使ってますか?CMS自体のコードを直接いじれたり、DBも自前で用意するので(とはいえサーバーレスでok)、少し手間はかかる分自由度が高くて便利です。
Strapiでコンテンツ更新のたびにサイトを自動でビルド・デプロイしたいので、最初は、StrapiのwebhookをNetlifyにつないで自動化してました。ところが、コンテンツが増えすぎてNetlifyのビルド制限に引っかかってしまい...。なんとかビルド時間を減らそうとしたのですが、単にコンテンツの数の暴力で歯がたちませんでした。
そこで、Netlifyでビルドするのではなく、GitHub Actionsでなんとかしようと思ったのですが、これがまた問題だらけでして。

問題点

StrapiのwebhookをGitHub Actionsのトリガーにしようとしたら、こんな壁にぶち当たりました:

  1. Strapiのwebhookって、リクエストBodyが空っぽなんです。というか設定ができない。
  2. でも、GitHub Actionsのrepository_dispatchイベントを使うには、Bodyにevent_typeを入れないといけない。

つまり、Strapiの標準webhookじゃGitHub Actionsを呼べないんですよ、、

解決策:Strapiにミドルウェアを追加しよう

ってことで、Strapiにカスタムミドルウェアを追加することにしました。この方法なら、Strapiのコア機能を直接変更せずに済みます。具体的にはこんな感じです:

  1. Strapiのsrcディレクトリに新しいミドルウェアファイルを作る
  2. このミドルウェアで、コンテンツが変わったらGitHub Actionsを呼び出す仕組みを実装する
  3. Strapiの設定ファイルに新しいミドルウェアを追加する

実装手順

  1. まず、/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();
    }
  };
};
  1. 次に、/config/middlewares.jsに以下のように追記します:
module.exports = [
  // ... 他のミドルウェア設定 ...
  {
    name: "global::triggerGithubActions",
    config: {},
  },
];

動作の仕組み

  1. Strapiでコンテンツが変更されると(POST, PUT, DELETE, PATCHリクエスト)、ミドルウェアが起動します。
  2. ミドルウェアは変更を検知し、一定時間(5分)の間に複数の変更があった場合はそれらをまとめて1回のアクションとして扱います(デバウンス処理)。今回のプロジェクトはStrapiのAPIを用いて複数のコンテンツを一度にPOSTしていたので、都度ビルドしてしまうのを防ぐためです。
  3. デバウンス処理後、GitHub APIを使って起動の合図を送ります。
  4. その合図で、設定しておいたGitHub Actionsのワークフローが動き出します。

注意点

  • 環境変数(GITHUB_TOKEN, GITHUB_OWNER, GITHUB_REPO)を適切に設定する必要があります。
  • GitHub Personal Access Tokenを使用するので、セキュリティには十分注意してください。
  • このミドルウェアは全てのコンテンツタイプに対して動作します。特定のコンテンツタイプのみに制限したい場合は、さらにロジックを追加する必要があります。

まとめ

この方法で、Strapiのコンテンツ更新をきっかけにGitHub Actionsを走らせられるようになりました。Netlifyの制限?もう知りません。柔軟なビルドとデプロイのワークフローが手に入りましたよ。
みなさんも、ヘッドレスCMSとCI/CDの連携で困ったら、このミドルウェアの追加を試してみてください!

ゆ
© 2024 by Yuichiro.