infralog

Cloudflare Pages で個人サイトを立てて詰まった6箇所

CloudflareAstro開発環境

目次
  1. ドキュメントに書いていない方で詰まる
  2. 1. Framework preset が見つからない
  3. 2. Node のバージョンは明示しないと合わない
  4. 3. Web Analytics は自動注入が強制されることがある
  5. 4. 独自ドメインのメールは無料で作れる(ただし受信専用)
  6. 5. .dev の空き確認に whois は使えない
  7. 6. HTTP 200 を成功条件にしてはいけない
  8. 検討したが採らなかった選択肢
  9. まとめ
  10. 関連記事

ドキュメントに書いていない方で詰まる

Astro の静的サイトを Cloudflare Pages に載せ、独自ドメインの割り当て、問い合わせ用メール、アクセス解析まで通した。手順自体は公式ドキュメントどおりで問題ない。詰まったのはそこに書かれていない挙動の方だった。

6箇所ある。UI の場所が想像と違うもの、選択肢が提示されず強制されるもの、そして自分の検証方法が間違っていたもの。

1. Framework preset が見つからない

Pages のプロジェクト作成は3ステップに分かれている。「Framework preset が無い」と感じるのは、Step 1(リポジトリ選択)を見ているからだ。Framework preset・Build command・Build output directory は Step 2(ビルド設定)にある。

もう一つ落とし穴がある。Cloudflare は Pages を Workers に統合しつつあり、Workers & Pages → Create の既定タブは Workers 側になっている。そちらの「Import a repository」に進むと、そもそも項目構成が違う。見分け方は単純で、

Pages のフローに直接入るなら /pages/new/provider/github を開けばいい。

なお preset は Build command と output を自動入力するだけのショートカットなので、手で入れるなら無くても困らない。Astro なら npm run builddist だ。

2. Node のバージョンは明示しないと合わない

環境変数 NODE_VERSION を設定しないと、Cloudflare 側の既定 Node でビルドされる。依存パッケージが新しい Node を要求していると、ここで食い違う。

手元の package.jsonengines を書いていなかったので、ロックファイルを見て必要な下限を確認した。テストランナーが >=22.12 を要求していたのに、デプロイ手順書には 20 と書いてあった。ローカルでは新しい Node で通っていたので気づかなかった。

"engines": { "node": ">=22.12" }

を足したうえで、Pages の環境変数にも NODE_VERSION = 22 を入れた。ビルドログに [email protected] と出れば効いている。

3. Web Analytics は自動注入が強制されることがある

Cloudflare Web Analytics には、スクリプトタグを自分で貼る方式と、プロキシが配信時に差し込む自動方式がある。ドメインが同じ Cloudflare アカウントのゾーンだと、自動方式しか提示されない。 トークンを取得する画面が出てこない。

これは、あらかじめレイアウトに beacon タグを書いておいた場合に問題になる。トークンが埋まらないまま残ると、無効なリクエストが毎ページ発生し、さらに自動注入と合わせて二重計上になる。タグは削除するしかない。

削除したあと、テストは「beacon タグが埋め込まれていないこと」を検証する形に反転させた。将来また誰かが手で足してしまうのを防ぐためだ。

代償として、Cloudflare のプロキシから離れると計測が止まる。「ホスティングを移しても計測が続くように手動タグにする」という当初の方針は、選択肢が無いので達成できなかった。

4. 独自ドメインのメールは無料で作れる(ただし受信専用)

問い合わせ用に新しいメールアドレスを作ろうとして、電話番号の制限で弾かれた。同じ番号で作れるアカウント数には上限がある。

ドメインを持っているなら Cloudflare Email Routing で足りる。[email protected] を作って既存の受信箱に転送でき、費用も電話番号も新規アカウントも要らない。公開されるのは転送先ではなく独自ドメイン側のアドレスだけだ。

制約は明確で、受信専用。送信は提供されないので、返信すると転送先のアドレスが相手に見える。公開されるわけではなく返信相手にだけ見える形なので、問い合わせ窓口としては実用上困らない。送信元も揃えたいなら外部の SMTP 中継が別途要る。

なお、プロキシ経由のゾーンでは Email Address Obfuscation が働き、HTML 中の mailto:/cdn-cgi/l/email-protection に書き換えられる。curl で見ると mailto: が見つからず一瞬焦るが、スクレイパー対策として正しく動いている。ブラウザでは JS が復号する。

5. .dev の空き確認に whois は使えない

.dev レジストリは whois を公開していない。ローカルの whois は IANA にフォールバックし、TLD 自体の情報を返す。ドメインが空いているかどうかは判定できない。

$ whois example-not-registered.dev
% IANA WHOIS server
domain:       DEV
organisation: Charleston Road Registry Inc.

RDAP を使う。

curl -sL -o /dev/null -w "%{http_code}" https://rdap.org/domain/example.dev
# 404 → 未登録 / 200 → 登録済み

-L を付け忘れると 302 が返って判定できない。また rdap.org は連続で叩くと 429 で止まる。

一次スクリーニングには DNS が速い。登録済みドメインはほぼ必ず NS を持つので、

dig +short NS example.dev @8.8.8.8

が空なら未委任=空きの可能性が高い、と当たりを付けてから RDAP で確定させる。レジストリ自身の RDAP エンドポイントは、登録済みドメインにも 404 を返すことがあったので当てにしなかった。

6. HTTP 200 を成功条件にしてはいけない

これが一番効いた。記事を push したあと、デプロイ完了を待つのにこう書いた。

until [ "$(curl -sL -o /dev/null -w '%{http_code}' https://example.com/posts/new-article/)" = "200" ]; do
  sleep 10
done

即座に抜けた。そしてサイトは古いままだった。

Cloudflare Pages は存在しないパスに対してインデックスページを 200 で返す。つまり、まだ存在しない新記事の URL を叩いても 200 が返る。ステータスコードで判定する限り、この条件は常に最初から真になる。

正しくは中身で判定する。

until curl -sL https://example.com/posts/new-article/ | grep -q "本文にしか出ない文字列"; do
  sleep 15
done

ダッシュボードを見に行くと、ビルドはまだ Queued ですらあった。「200 が返った=デプロイできた」と報告する直前だった。

ついでにもう一点。デプロイ直後に sitemap を確認したら新記事が載っていなかったが、これは CDN キャッシュだった。?cb=$RANDOM を付けて取り直したら入っていた。こちらも「見えない=無い」ではない。

検討したが採らなかった選択肢

Workers の Static Assets で配信する。 Astro の静的サイトは Workers でも動く。ただし wrangler.jsonc が必要になり、設定の考え方も手順書も変わる。静的サイトを置くだけなら Pages の方が素直なので採らなかった。統合が進んで Pages 側が畳まれたら再検討する。

問い合わせをフォームサービスに変える。 mailto: を晒さずに済むが、Cloudflare Pages には無料のフォームハンドラが無く、Worker を書くか外部サービスを使うことになる。「初期費用はドメイン代のみ」という制約があったので見送った。

送信も独自ドメインに揃える。 外部の SMTP 中継と Gmail の「他のアドレスから送信」を組み合わせれば可能だが、設定項目が増える割に、受信できていれば窓口としては成立する。後回しにした。

まとめ

関連記事