Astro + MicroCMSで開発した個人ブログのビルドを高速化するtips
先日、私の個人ブログをNext.jsからAstroに移行しました。
無事に移行はできたものの、思ったよりもビルドに時間がかかるなという印象でした。以下は移行当初のCloudflare Pagesのビルドログです。
私の個人ブログは全部で50ほどのコンテンツしかないにも関わらず、ビルド開始から完了までに1分近く時間がかかっています。記事ページ1つをビルドするのに約1秒かかっており、記事数の増加に伴いビルド時間も増えていくことが目に見えていたため対応が必要でした。
原因を調査して改良を進めた結果、約1分かかっていたビルドが5秒前後で完了するようになりました。以下は改良後のビルドログです。
ここではAstroのビルドを高速化するために実施した改修内容について紹介します。
GetStaticPathsの戻り値propsを利用してデータ取得用apiの実行回数を減らす
改良前の記事詳細ページのコードは以下のようになっていました。(イメージ)
動的ルーティングのためにgetStaticPaths
で登録済みの全記事のidを返しています。
---
export async function getStaticPaths() {
// getAllArticleIds は全記事のidをapiで取得する関数
const ids = await getAllArticleIds()
return ids.map((id) => ({
params: {
id
}
}));
}
const { id } = Astro.params;
// getArticle はidをもとに記事の詳細情報をapiで取得する関数
const article = await getArticle(id);
---
// 詳細ページの描画...
上記のコードは、ビルド時に以下のように処理されます。
-
getStaticPaths
でgetAllArticleIds
が1回実行される - 記事ページの生成時、生成対象のページの数だけ
getArticle
が実行される
「2」より、記事の増加に伴ってapiの実行回数が増えてしまい、ビルド時間が遅くなるという問題があります。
これは以下のようなコードに修正することで解消しました。
---
export async function getStaticPaths() {
// getAllArticles は登録済みの全記事の詳細情報をapiで取得する関数
const articles = await getAllArticles()
return articles.map((article) => ({
params: {
article.id,
},
props: {
// 取得した全記事情報から、idをもとに対象の記事情報を特定してpropsに設定
article: allArticles.find((x) => article.id === x.id) as Article
},
}));
}
// props経由で記事情報を取得
const { article } = Astro.props;
---
// 詳細ページの描画...
GetStaticPaths
は、動的ルーティングに必要なparams
の他に、生成対象のページに渡す情報をprops
で返すことができます。(Next.jsとは異なる点ですね)
改良後のコードはビルド時に以下の様に処理されます。1度のapi実行で済むため、高速に動作します。
-
getStaticPaths
でgetAllArticles
が1回実行される - 記事ページの生成時、
getStaticPaths
の戻り値であるprops
に設定された記事情報をもとにページを生成する
私のように記事の管理にMicroCMSをお使いであれば、全ての記事情報を取得する処理はmicrocms-js-sdkのgetAllContents(v2.7.0から追加)を使うことで実現可能です。
apiの実行結果をキャッシュする
私の個人ブログは、ほぼ全てのページにサイドバーが存在します。サイドバーにはカテゴリとタグが表示されていますが、これはMicroCMS側の情報をapiで取得することで実現しています。
改良前は以下のような実装になっていました。(イメージ)
---
import SideBar from "@/components/sidebar.astro";
---
<div>
<main>
<slot />
</main>
<aside>
<SideBar />
</aside>
</div>
---
import { GetCategories, GetTags } from "@/libs/MicroCMS/Client";
// すべてのタグ情報とカテゴリ情報をapiで取得
const [tags, categories] = await Promise.all([GetTags(), GetCategories()])
---
// サイドバーの描画
// microcms-js-sdkを使ったデータ取得...
export const GetCategories = () => {
return client.getAllContents<Category>(...)
};
export const GetTags = () => {
return client.getAllContents<Tag>(...)
};
上記のGetTags
およびGetCategories
はタグとカテゴリの情報をapiで取得する関数です。ほぼすべてのページにサイドバーが存在することから、これらの関数はビルド時にページを生成するたびに実行されます。
サイドバーはすべてのページで全く同じ内容であるにもかかわらず、ページを生成するたびにapiを実行してしまっているという状態のため対応が必要です。
対応にあたり、まずapiの実行結果をキャッシュするための以下のような処理を用意しました。
let cacheData: { [key: string]: any } = {};
const cache = async <T>(key: string, factory: () => Promise<T>) => {
// キャッシュにない場合はデータ取得のための関数を実行して結果をキャッシュ
if (!cacheData[key]) {
cacheData[key] = await factory();
}
return cacheData[key] as T;
};
export default cache;
そのうえで、MicroCMS側のデータを取得する関数を以下の様に修正しました。
+ import cache from "@/libs/cache";
export const GetCategories = () => {
+ return cache("allCategoryList", () =>
client.getAllContents<Category>(...),
+ );
};
export const GetTags = () => {
+ return cache("allTagList", () =>
client.getAllContents<Tag>(...),
+ );
};
こうすることで、初回のみapiを実行して結果をキャッシュし、2回目以降はキャッシュを参照してデータを返すため高速に動作するようになります。
まとめ
私の場合、上記2つがAstroのビルド時間の短縮に非常に有効でした。移行した当初は思ったよりビルド時間が遅かったためAstroのせいにしそうでしたが、そんなことは全くなく自分のコードが悪いだけでした(泣)
この記事が参考になれば嬉しいです。
Discussion