💡

Astro + microCMS + Cloudflare PagesだとSSRモードが使えなかった問題とその解決策

2023/04/26に公開

発端

個人用にAstromicroCMSを使ってブログを制作していたのだが、Astro公式が用意した@astrojs/cloudflareアダプターを使ったSSR(サーバーサイドレンダリング)モードでのCloudflare Pagesへのデプロイにおいて以下のエラーが起こってデプロイに失敗してしまう。
https://docs.astro.build/ja/guides/integrations-guide/cloudflare/

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の設定にて設定済み)
https://github.com/excelsior091224/astro_test/blob/master/src/library/microcms.ts

const client = createClient({
  serviceDomain: import.meta.env.MICROCMS_SERVICE_DOMAIN,
  apiKey: import.meta.env.MICROCMS_API_KEY,
});

(ブログの作り方は以下の記事のものを参考にしている)
https://blog.microcms.io/astro-microcms-introduction/
https://qiita.com/Michinosuke/items/09c30fde4ca7168f96ef
その後、アダプターを使わなくても普通にSSG(静的サイト生成)モードでデプロイできる事がわかった(というより、AstroやSSGといったものを使うのが初めてだったので「Cloudflare Pagesにデプロイする場合アダプターを使ってSSRモードにしないとデプロイできない」と勝手に勘違いしてた)のでその方式に移行したのだが、SSRモードが使えないというのがどうも引っかかったので、別途テスト用のブログを作って検証を行っていた。
https://github.com/excelsior091224/astro_test

GitHubのissueに上げてみた

だが、どうやっても「SSRで環境変数が読めない」問題が解決できない。
下記のissueではastro.config.mjsで以下のようにviteで定義すれば読めるという話もあったが、それもどうもうまくいかない。
https://github.com/withastro/astro/issues/5234

vite: {
    define: {
      "process.env.SECRET": process.env.SECRET,
    }
}

https://github.com/AirBorne04/astro-cloudflare-env/blob/main/astro.config.mjs
仕方がないのでAstroのGitHubリポジトリにissueを上げてみた。
https://github.com/withastro/astro/issues/6130
すると、以下のように開発側から回答があった。

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を変更した。
https://github.com/excelsior091224/astro_test/blob/ec388d9cb34671174308fcf9557b43d78b945efd/src/library/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エラーが出て何も表示されない。
https://1a7a4f5a.astro-test-b4j.pages.dev/blog

Cloudflare側で何か問題が起きているようだ(結局環境変数を読めていない?)が、Cloudflare Pagesではエラーログを見る機能がないのでどうしようもない。詰んだ。
現状わかっているのは、Cloudflare PagesにAstroとmicroCMSを使ったアプリをデプロイする場合、SSRモードは使えないということだけだ。読者の皆さんのほうで何かいい解決法が見つかったら、ぜひZennなりQiitaなりで公開して共有してほしい。

解決(2023/04/27追記)

microCMSのdiscordコミュニティにてhimorishigeさんが解決策を示してくれた。
https://discord.com/channels/1029590791997358101/1052485486758854686/1100970337253986344

だいぶ以前のメッセージに対しての返信ですみません🙏
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

これに基づいて、コードを下記のように変更したところ、無事に稼働するようになった。
https://github.com/excelsior091224/astro_test/blob/c2edbd8e42e011c3d05ef49880b181c369e7ea53/src/library/microcms.ts
https://github.com/excelsior091224/astro_test/blob/4c013eda634be0aa4adbcb21763fd0929ed2f987/src/pages/blog.astro
https://github.com/excelsior091224/astro_test/blob/c2edbd8e42e011c3d05ef49880b181c369e7ea53/src/pages/blog/[blogId].astro
https://d7fb037a.astro-test-b4j.pages.dev/blog

また、ログの問題に関しても、デプロイのリアルタイムログから見ることができるという。
https://discord.com/channels/1029590791997358101/1052485486758854686/1100973060561055814

すでにお使いかもしれませんが、リアルタイムログからログストリームをブラウザ閲覧することができます。
確認できるのはブラウザで閲覧している間のログだけになりますがデプロイしたものをデバッグするのに役立つのでおすすめです。

(画像は上記コミュニティより)

ここまで手厚くサポートしてくれるとは……
microCMS最高! みんなも使おうぜ!

追記(2023/08/25)

https://zenn.dev/hideokamoto/scraps/ee2aafe07abe44
hidetaka okamotoさんが同じ問題に対処しています。こちらのコードではAstro2.0以降で使えるHybrid Renderingにも対応できるようです。

追追記(2023/10/19)

いつの間にか仕様変更があったらしくてgetRuntime()ではなくAstro.locals.runtimeでruntimeを取ってくるようになったらしい。
https://docs.astro.build/en/guides/integrations-guide/cloudflare/#cloudflare-runtime
そのため、コードを以下のように書き換えた。
https://github.com/excelsior091224/astro_test/blob/9417ec81b98a309efe4e7ef8117d652df9fd4d04/src/library/microcms.ts
https://github.com/excelsior091224/astro_test/blob/9417ec81b98a309efe4e7ef8117d652df9fd4d04/src/pages/blog/[blogId].astro
https://github.com/excelsior091224/astro_test/blob/2f94f94125de786ae08a7fcd5e56b7e3d547d684/src/pages/blog.astro
SSGとSSRの両対応させようとしてCMSBlogクラス内の関数の引数を変えてしまった結果、pagesディレクトリ内のコードまで変えなくてはいけなくなってしまったが……。

Discussion