🏗️

AstroとヘッドレスCMSのビルド時間を短縮してみた

2024/10/23に公開1

最近Astroを使い始めました。
以前はNuxtを使っていたのですが、Nuxtが使いづらいな〜と思っていた矢先にAstroはかなり開発体験も良く、ビルド時間も短縮できておそらく2024年時点では最高の静的サイト生成フレームワークだと思います。
実際にサイトを開発している中でページ数が多くなるとビルド時間が長くなる問題があるかと思います。
特に今回作ったサイトでは日本語、英語に加え、中国語、韓国語の計4つの言語に対応しなくてはいけません。
仮に、トップ、アバウト、プロジェクト(40ページ)、コンタクトの普通のコーポレートであれば生成されるページ数は100を超えます。
今回はそんなサイトのビルド時間を4.5m → 1mまで短縮した方法を備忘録として記載していきます。

前提条件

今回は以下の構成で実装しました。

  • node@18.18.2
  • Astro@4.16.6
  • microcms-js-sdk@3.1.2

CMSはたくさんありますが、microcmsを使います。

Astroにはstoreが標準搭載していない。

始めに、設計を結論として書いていきます。
言葉で書いていくのも面倒なので図にして説明します。
シンプルに話すとページ毎にCMSからデータを取得するのではなく、layout.astroや共通するファイルでデータをロードしてしまおうという考えです。

以前までは上記のように、ページ毎のastroのファイルのところにCMSからデータを取得するように書いていました。
しかしこの方法だとビルド時にページ毎にAPIからデータを取得するため、時間がかかっていました。
これが サービスページならServiceのAPIだけから取得 とかならまだいいのですが、トップページでAbout,News,Serviceのデータを取得とかにすると時間がかかります。

そこで上記のようにlayout.astroなどの共通してるファイルで一発で全データを取得して他ページで使いまわすという手法にシフトしました。

これがNuxtであれば、store/index.jsでヘッドレスCMSからの情報を取得するコードを書いて nuxtServerInit とかで取得すれば同じようなことができました。
storeで保存しておけば全体で使えるので便利でしたが、Astroではそうは行きません。

キャッシュを作成してそこに保存する

キャッシュを作成と書くと少し難しく感じるかもしれませんが、シンプルに変数にオブジェクトを代入するだけです。

src/lib/cms.js
import { createClient } from 'microcms-js-sdk';

const client = createClient({
  serviceDomain: 'domain',
  apiKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
})

const cms = {
  load: false,
  about: {},
  work: [],
}

export const setCms = async () => {
  if(cms.load) return
  cms.about = await setAbout()
  cms.work = await setWork()
  cms.load = true
}

// Get Work
export const setWork = async () => {
  const data = await client.get({
    endpoint: 'works', // あなたのエンドポイントに置き換えてください
    queries: {
      limit: 100,
    }
  }).then((res) => {
    return res
  });
  return data.contents
}
export const getWork = async () => {
  return cms.work
}

// Get About
export const setAbout = async () => {
  const data = await client.get({
    endpoint: 'about', // あなたのエンドポイントに置き換えてください
  }).then((res) => {
    return res
  });
  return data
}
export const getAbout = async () => {
  return cms.about
}
src/layouts/Layout.astro
---
import { setCms } from '@/lib/cms';
await setCms()
---

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
    <title>Site Title</title>
  </head>
  <body>
    <main>
      <slot />
    </main>
  </body>
</html>

セットアップとしてはこんな感じです。cms.jsで cmsというオブジェクトにBooleanのフラグを持たせます。setCMS内で フラグを起動させ初回以降は起動しないようにすることでサイトをビルドする時しかデータを取得しません。
またgetWokrs関数のようにデータを取得するのに関数を使っているのは柔軟にデータを書き換えれるようにするためです。

src/pages/works/index.astro
---
import { getAbout,getWork } from '@/lib/cms'

const workList = await getWork()
---

<div class="container">
{
  workList.map((item) => {
    return <PartsItem item={item}/>
  })
}
</div>

ページの中ではcms.jsからそれぞれのデータを取得したものを参照してサイト内で扱うことができます。

計測

Before

After

上記の方法にするだけでビルド時間を4分48秒から1分19秒、約27%ほど減らすことができました。
今回私がTypeScriptを書けないのでバニラjsで書きましたが、型チェックなどを持たせたいときは全然TSでもいいと思います。

それではみなさん良い一日を。

Discussion

naarynaary

いま自分のサイトでNext.jsからAstroへのリプレイスを進めていて、複数ページで同じエンドポイントのデータを使用する部分はこの方法を参考にさせていただきました。ありがとうございます!