はきくま!制作日記:Cloudflare 編

投稿日

Astro で生成した静的サイトを Cloudflare Workers で公開するまでに、つまずいたところ。

microCMS ──(API)──▶ ビルド時に取得 ──▶ 静的HTML(dist/) ──▶ Cloudflare Workers

その1:Cloudflare のビルドだけが Invalid media query で落ちる

ローカルの npm run build は通るのに、Cloudflare のビルドだけが落ちる。

[lightningcss minify] Invalid media query
  Location: undefined:8:17

Vite は CSS の圧縮に lightningcss を使っていて、生成された CSS の構文が不正だと落ちる。疑って潰していった順番:

  1. package-lock.json がコミットされていて、依存のバージョンは固定されているか
  2. Node のバージョン差。ビルド用変数に NODE_VERSION を設定。パッチまで指定(22.2.2)するとビルド環境にない版でインストールに失敗するので、メジャーだけ(22)にする
  3. vite.build.cssMinify を esbuild に切り替え
  4. メディアクエリをすべて外したブランチを push

どれも効かず、最終的に同じ構成で動いている別プロジェクトの astro.config.mjs をそのまま移植して通った。原因の一行は特定しないまま。後日、日を置いてリトライしただけでエラーが消えた時もあり、Cloudflare 側の環境やビルドキャッシュの影響もありそう。

学び

  • ローカル(macOS)と Cloudflare(Linux)で結果が違う時は、依存の固定・Node のバージョン・設定ファイルの差の順に疑う
  • 動いている実物があるなら、設定ファイルごと比べるのが一番早い

その2:astro add cloudflare と wrangler.jsonc の名前

デプロイで Missing file or directory: public/.assetsignore。Cloudflare 側の wrangler deploy が、毎回「初回セットアップ」だと判断してウィザードを走らせていた。

このサイトは、CSS をユーティリティクラス中心で管理したくて作った、自作の Astro テンプレートから始めている。

astro add cloudflare は、wrangler.jsonc の name に package.json の name を書き込む。テンプレートから作ったプロジェクトだと、そこがテンプレートの名前のままで、Cloudflare 側の Worker 名と食い違う。

{
  "name": "hakikuma-site", // Cloudflare 側の Worker 名と一致させる
  "main": "@astrojs/cloudflare/entrypoints/server",
  "assets": { "directory": "./dist", "binding": "ASSETS" }
}

テンプレートから新しく作るたびに直すのを忘れないよう、package.json と wrangler.jsonc の name を一括で書き換える scripts/setup.mjs を用意した。詳細は別途記事を作成する。


その3:Workers に環境変数が入らない

ブランチのビルドで microCMS のクライアントが parameter is required (check serviceDomain and apiKey)。ダッシュボードで変数を足そうとすると、

Variables cannot be added to a Worker that only has static assets.

いったんは Pages に作り直す方向で進めたが、よく考えると microCMS のキーが必要なのはビルドの時だけ。公開後の Worker は静的ファイルを配るだけで、キーは使わない。

Workers の設定画面には変数の欄が二つある。

  • Runtime variables and secrets:公開後の Worker が動く時の変数。静的アセットのみの Worker には入れられない
  • Builds → Variables and secrets:ビルドの時の変数

後者に MICROCMS_SERVICE_DOMAIN と MICROCMS_API_KEY(Secret)を入れたら、そのまま通った。Pages への移行は取りやめ。

学び

  • 静的サイト生成なら、外部APIのキーはビルド用の変数に置く
  • キーを変えた時は、ローカルの .env と Builds の変数の両方を直す

その4:ブランチごとのプレビュー

Builds の「Builds for non-production branches」を有効にしておくと、main 以外のブランチを push した時もビルドが走り、ブランチごとのプレビューURLが発行される。

  • 本番に影響を出さずに、原因の切り分け用のブランチ(メディアクエリを全部外した版など)を Cloudflare 上で試せる
  • クラウド上で作業してもらったブランチも、pull せずにスマホでプレビューを確認できる

その5:更新のたびの再ビルド(予定)

ページは getStaticPaths でビルド時に静的生成しているので、読者がページを開いても microCMS の API は叩かれない。そのぶん、microCMS で記事を更新したら再ビルドが必要になる。

  • Cloudflare の Builds → Deploy Hooks でビルド開始用のURLを発行
  • microCMS の各 API の Webhook に登録(公開・公開終了・削除のタイミングで発火)
  • 作品・話・カップリングの全 API に設定しないと、どれかを直した時に反映されない
  • フックのURLは知っていれば誰でもビルドを走らせられるので、外に出さない

Cloudflare 編の学び

  • 静的サイト生成では、「ビルド時に必要なもの」と「公開後に必要なもの」を分けて考える
  • ローカルと本番で結果が違う時は、依存の固定・ランタイムのバージョン・設定ファイルの差から疑う
  • 原因の一行を突き止めるより、動く状態に戻すことを優先していい

追記

その6:Invalid media query の再発(@media (hover: hover))

ホームの小説カードに「ホバーでサムネイルが背景に出る」処理を足したところ、その1と同じエラーで Cloudflare のビルドが落ちた。

[vite] ✗ Build failed in 814ms
[lightningcss minify] Invalid media query
  Location: undefined:8:17
  • 落ちたのは「Building static entrypoints」の段階。CSS の圧縮(finalizeCss)で止まっていて、場所はファイル名なしの undefined:8:17 だけ
  • ローカルでは出なかった。開発サーバー(npm run dev)は CSS を圧縮しないので、これだけでは気づけない
  • grep -rn "@media" src で直書きの @media を探すと、_mixin.scss の m.mq の中と、追加した index.astro の1か所だけだった
  • 追加した @media (hover: hover) { &:hover::before { … } } のブロックを消したら、ビルドが通った

原因の一行は特定できていない。構文は正しいので、見えない文字(全角スペースや NBSP)が混ざっていた可能性を疑ったが、確認はしていない。その1と同じ症状だが、同じ原因かどうかも分からない。

現状は @media なしで、:hover だけで動かしている。PC では問題ない。スマホ(ホバーのない端末)で、タップ後にホバー状態が残る可能性がある。

学び

  • npm run dev が通っても、圧縮を通すビルドで落ちる CSS がある。push する前に npm run build も通す
  • 場所の出ない圧縮エラーは、新しく足したブロックから外して切り分ける。ブランチごとのプレビュー(その4)があるので、本番に影響なく試せる
  • 直書きの @media は少ないので、grep -rn "@media" src ですぐ洗える

その7:shiki を入れた時の注意

日記のコードブロックの色付けに shiki を使った。

  • @astrojs/cloudflare を使っていると、shiki 標準の WASM エンジンが動かない、または不安定になることがある。JavaScript 版のエンジン(shiki/engine/javascript)にしてある
  • JavaScript 版のエンジンにした状態で、Cloudflare 上のビルドも表示も問題なく通った
  • 日記のページは getStaticPaths でビルド時に静的生成しているので、shiki はビルド時だけ動く