Astro + microCMS + Cloudflare PagesだとSSRモードが使えなかった問題とその解決策
発端
個人用にAstroとmicroCMSを使ってブログを制作していたのだが、Astro公式が用意した@astrojs/cloudflareアダプターを使ったSSR(サーバーサイドレンダリング)モードでのCloudflare Pagesへのデプロイにおいて以下のエラーが起こってデプロイに失敗してしまう。
Error: Failed to publish your Function. Got error: Uncaught Error: parameter is required (check serviceDomain and apiKey)
at worker.mjs:83:3411 in kr
at worker.mjs:436:477
どうもmicroCMSのAPIを叩く過程においてサービスドメインとAPIキーの環境変数を読めないせいで失敗しているようだ。
(環境変数はCloudflare Pagesの設定にて設定済み)
const client = createClient({
serviceDomain: import.meta.env.MICROCMS_SERVICE_DOMAIN,
apiKey: import.meta.env.MICROCMS_API_KEY,
});
(ブログの作り方は以下の記事のものを参考にしている)
その後、アダプターを使わなくても普通にSSG(静的サイト生成)モードでデプロイできる事がわかった(というより、AstroやSSGといったものを使うのが初めてだったので「Cloudflare Pagesにデプロイする場合アダプターを使ってSSRモードにしないとデプロイできない」と勝手に勘違いしてた)のでその方式に移行したのだが、SSRモードが使えないというのがどうも引っかかったので、別途テスト用のブログを作って検証を行っていた。GitHubのissueに上げてみた
だが、どうやっても「SSRで環境変数が読めない」問題が解決できない。
下記のissueではastro.config.mjs
で以下のようにviteで定義すれば読めるという話もあったが、それもどうもうまくいかない。
vite: {
define: {
"process.env.SECRET": process.env.SECRET,
}
}
仕方がないのでAstroのGitHubリポジトリにissueを上げてみた。 すると、以下のように開発側から回答があった。
Hi,
I understand it is a little confusing, and we would like to change this behavior, but unfortunately we can't.
On Cloudflare the env vars are passed to the request and cant be accessed before the first request. Therefore they are undefinded in you example, this limitation is mentioned also in the docs
こんにちは、
少し混乱していることは理解していますし、この動作を変更したいのですが、残念ながらできません。
Cloudflareでは、env varsはリクエストに渡され、最初のリクエストの前にアクセスすることはできません。そのため、この例では未確定となっています。この制限については、ドキュメントにも記載されています。
(DeepLにて翻訳)
Cloudflare側の仕様だからどうしようもないということだった。
「じゃあどうすればいいの?」と聞いたところ、「ファクトリ関数を使ってクライアントインスタンスを保管すればうまくいくだろう」ということだった。そのため、先方のアドバイスを元に以下のようにmicrocms.ts
を変更した。
const clientFactoryFunction = () => {
return createClient({
serviceDomain: import.meta.env.MICROCMS_SERVICE_DOMAIN,
apiKey: import.meta.env.MICROCMS_API_KEY,
});
}
export const getBlogs = async (queries?: MicroCMSQueries) => {
const client = clientFactoryFunction();
const data = await client.get<BlogResponse>({ endpoint: "blogs", queries });
if (data.offset + data.limit < data.totalCount) {
queries ? (queries.offset = data.offset + data.limit) : "";
const result: BlogResponse = await getBlogs(queries);
return {
offset: result.offset,
limit: result.limit,
contents: [...data.contents, ...result.contents],
totalCount: result.totalCount,
};
}
return data;
};
export const getBlogDetail = async (
contentId: string,
queries?: MicroCMSQueries
) => {
const client = clientFactoryFunction();
return await client.getListDetail<Blog>({
endpoint: "blogs",
contentId,
queries,
});
};
すると、一応デプロイは成功したように思われた。
だが、肝心のmicroCMSからの記事を表示する部分(/blog/
以下の階層)にアクセスしようとすると以下のように500エラーが出て何も表示されない。
Cloudflare側で何か問題が起きているようだ(結局環境変数を読めていない?)が、Cloudflare Pagesではエラーログを見る機能がないのでどうしようもない。詰んだ。
現状わかっているのは、Cloudflare PagesにAstroとmicroCMSを使ったアプリをデプロイする場合、SSRモードは使えないということだけだ。読者の皆さんのほうで何かいい解決法が見つかったら、ぜひZennなりQiitaなりで公開して共有してほしい。
解決(2023/04/27追記)
microCMSのdiscordコミュニティにてhimorishigeさんが解決策を示してくれた。
だいぶ以前のメッセージに対しての返信ですみません🙏
AstroのSSRはCloudflare PagesだとPages Functions(非Node.js環境)を利用して動作します。
その際、環境変数の扱いが変わるのでimport.meta
は利用できず、ご記載の通りリクエストごとに取得する必要があると思います。
AstroだとAstro.request
にてリクエストを受け取れるので、以下のような手順で試してみてはいかがでしょうか。
以下microCMSのチュートリアル記事のソースを利用しています(https://blog.microcms.io/astro-microcms-introduction)
1)
src/pages/index.astro
からAstro.request
を取得関数へ渡すconst response = await getBlogs(Astro.request, { fields: ["id", "title"] });
src/library/microcms.ts
// Clientインスタンス作成用の関数(チュートリアルのコードに新規追加) const generateClient = (request: Request) => { // runtimeの中にはenvの他、KVなど他のサービスなどがコンテキストとして入ってくる const runtime = getRuntime(request); //@ts-expect-error envの型は適宜調整 const { MICROCMS_SERVICE_DOMAIN, MICROCMS_API_KEY } = runtime.env; return createClient({ serviceDomain: MICROCMS_SERVICE_DOMAIN, apiKey: MICROCMS_API_KEY, }); }; //APIの呼び出し export const getBlogs = async (request: Request, queries?: MicroCMSQueries) => { const client = generateClient(request); return await client.get<BlogResponse>({ endpoint: "blogs", queries }); };
参考)
Access to the Cloudflare runtime
https://docs.astro.build/ja/guides/integrations-guide/cloudflare/#access-to-the-cloudflare-runtime
これに基づいて、コードを下記のように変更したところ、無事に稼働するようになった。
また、ログの問題に関しても、デプロイのリアルタイムログから見ることができるという。
すでにお使いかもしれませんが、リアルタイムログからログストリームをブラウザ閲覧することができます。
確認できるのはブラウザで閲覧している間のログだけになりますがデプロイしたものをデバッグするのに役立つのでおすすめです。
(画像は上記コミュニティより)
ここまで手厚くサポートしてくれるとは……
microCMS最高! みんなも使おうぜ!
追記(2023/08/25)
hidetaka okamotoさんが同じ問題に対処しています。こちらのコードではAstro2.0以降で使えるHybrid Renderingにも対応できるようです。
追追記(2023/10/19)
いつの間にか仕様変更があったらしくてgetRuntime()
ではなくAstro.locals.runtime
でruntimeを取ってくるようになったらしい。
そのため、コードを以下のように書き換えた。
SSGとSSRの両対応させようとしてCMSBlog
クラス内の関数の引数を変えてしまった結果、pages
ディレクトリ内のコードまで変えなくてはいけなくなってしまったが……。
Discussion