Astro + Cloudlare PagesでSSGとSSRを併用するときの注意点
追記
@astrojs/cloudflare@6.7.0で大幅に改良されました!
簡単な構成では無駄のない記述になることを確認しました。
**/index.html
の削除を始め、exclude主体で設定したところをinclude主体に変更したようです。
親はSSRで子がSSG(もしくはその逆)にした場合、どのような構成になるか確認していきたいですね。
AstroとCloudflare最高!
いいところ
- Astroのビルド爆速、動的で変更が多いところはSSRすればいいし便利
- hybridモードでやれば、大体SSGで一部SSRとか簡単
- Next.js(App Router)は静的なのか動的なのかよくわからなかった(実力不足)
- Cloudflare Pages無料でほぼ制限なく運用できるのやばい
- Workersも1日10万件あればSSRしてもほぼ超えない(多分)
- https://www.cloudflare.com/ja-jp/plans/developer-platform/
罠
output: hybrid
でSSRしつつ、100件ほどSSGしたところローカルではビルドできるのに、Pages側のデプロイに失敗する事象が発生しました。デプロイのログを確認してみるとこんな一文が…
Error: Failed to publish your Function. Got error: Error 8000057: Rules in
_routes.json
are over the 100 rule limit. Refer to https://cfl.re/3FsE4aF.
どうやらWorkersを経由するか定義する_route.json
のルールは100個までのようで、SSGでHTMLを生成した際に上限の100個を超えてしまったようです。dist/_route.json
を確認したところ、静的に生成した全てのディレクトリとファイルが羅列されており、非常に無駄の多い記述となっていました。
補足
Pagesとは異なり無料プランではWorkersは通信量や通信数に制限があります。そのため、極力Workersを経由せずPagesの静的に生成したファイルにルーティングしたいです。そのための設定を記述するのが_route.json
です。exclude: {...}
に記述したパスはPagesに直接ルーティングされるようになります。
自動生成されたファイル
{
"version": 1,
"include": [
"/*"
],
"exclude": [
"/404.html",
"/_astro/404.9a2b609c.css",
"/_astro/Counter.eb2f7b54.js",
"/_astro/client.8a0a7111.js",
"/_astro/hello.32e98718.css",
"/_astro/index.ea0020fc.js",
"/_worker.js",
"/asset/manifest.json",
"/counter/index.html",
"/favicon/android-chrome-128x128.png",
"/favicon/android-chrome-144x144.png",
"/favicon/android-chrome-152x152.png",
"/favicon/android-chrome-192x192.png",
"/favicon/android-chrome-256x256.png",
"/favicon/android-chrome-36x36.png",
"/favicon/android-chrome-384x384.png",
"/favicon/android-chrome-48x48.png",
"/favicon/android-chrome-512x512.png",
"/favicon/android-chrome-72x72.png",
"/favicon/android-chrome-96x96.png",
"/favicon/apple-touch-icon-114x114-precomposed.png",
"/favicon/apple-touch-icon-114x114.png",
"/favicon/apple-touch-icon-120x120-precomposed.png",
"/favicon/apple-touch-icon-120x120.png",
"/favicon/apple-touch-icon-144x144-precomposed.png",
"/favicon/apple-touch-icon-144x144.png",
"/favicon/apple-touch-icon-152x152-precomposed.png",
"/favicon/apple-touch-icon-152x152.png",
"/favicon/apple-touch-icon-180x180-precomposed.png",
"/favicon/apple-touch-icon-180x180.png",
"/favicon/apple-touch-icon-57x57-precomposed.png",
"/favicon/apple-touch-icon-57x57.png",
"/favicon/apple-touch-icon-60x60-precomposed.png",
"/favicon/apple-touch-icon-60x60.png",
"/favicon/apple-touch-icon-72x72-precomposed.png",
"/favicon/apple-touch-icon-72x72.png",
"/favicon/apple-touch-icon-76x76-precomposed.png",
"/favicon/apple-touch-icon-76x76.png",
"/favicon/apple-touch-icon-precomposed.png",
"/favicon/apple-touch-icon.png",
"/favicon/favicon.ico",
"/favicon/icon-128x128.png",
"/favicon/icon-144x144.png",
"/favicon/icon-152x152.png",
"/favicon/icon-160x160.png",
"/favicon/icon-16x16.png",
"/favicon/icon-192x192.png",
"/favicon/icon-196x196.png",
"/favicon/icon-24x24.png",
"/favicon/icon-256x256.png",
"/favicon/icon-32x32.png",
"/favicon/icon-36x36.png",
"/favicon/icon-384x384.png",
"/favicon/icon-48x48.png",
"/favicon/icon-512x512.png",
"/favicon/icon-72x72.png",
"/favicon/icon-96x96.png",
"/favicon/site-tile-150x150.png",
"/favicon/site-tile-310x150.png",
"/favicon/site-tile-310x310.png",
"/favicon/site-tile-70x70.png",
"/hello/index.html",
"/ssg/1/index.html",
"/ssg/10/index.html",
"/ssg/100/index.html",
"/ssg/11/index.html",
"/ssg/12/index.html",
"/ssg/13/index.html",
"/ssg/14/index.html",
"/ssg/15/index.html",
"/ssg/16/index.html",
"/ssg/17/index.html",
"/ssg/18/index.html",
"/ssg/19/index.html",
"/ssg/2/index.html",
"/ssg/20/index.html",
"/ssg/21/index.html",
"/ssg/22/index.html",
"/ssg/23/index.html",
"/ssg/24/index.html",
"/ssg/25/index.html",
"/ssg/26/index.html",
"/ssg/27/index.html",
"/ssg/28/index.html",
"/ssg/29/index.html",
"/ssg/3/index.html",
"/ssg/30/index.html",
"/ssg/31/index.html",
"/ssg/32/index.html",
"/ssg/33/index.html",
"/ssg/34/index.html",
"/ssg/35/index.html",
"/ssg/36/index.html",
"/ssg/37/index.html",
"/ssg/38/index.html",
"/ssg/39/index.html",
"/ssg/4/index.html",
"/ssg/40/index.html",
"/ssg/41/index.html",
"/ssg/42/index.html",
"/ssg/43/index.html",
"/ssg/44/index.html",
"/ssg/45/index.html",
"/ssg/46/index.html",
"/ssg/47/index.html",
"/ssg/48/index.html",
"/ssg/49/index.html",
"/ssg/5/index.html",
"/ssg/50/index.html",
"/ssg/51/index.html",
"/ssg/52/index.html",
"/ssg/53/index.html",
"/ssg/54/index.html",
"/ssg/55/index.html",
"/ssg/56/index.html",
"/ssg/57/index.html",
"/ssg/58/index.html",
"/ssg/59/index.html",
"/ssg/6/index.html",
"/ssg/60/index.html",
"/ssg/61/index.html",
"/ssg/62/index.html",
"/ssg/63/index.html",
"/ssg/64/index.html",
"/ssg/65/index.html",
"/ssg/66/index.html",
"/ssg/67/index.html",
"/ssg/68/index.html",
"/ssg/69/index.html",
"/ssg/7/index.html",
"/ssg/70/index.html",
"/ssg/71/index.html",
"/ssg/72/index.html",
"/ssg/73/index.html",
"/ssg/74/index.html",
"/ssg/75/index.html",
"/ssg/76/index.html",
"/ssg/77/index.html",
"/ssg/78/index.html",
"/ssg/79/index.html",
"/ssg/8/index.html",
"/ssg/80/index.html",
"/ssg/81/index.html",
"/ssg/82/index.html",
"/ssg/83/index.html",
"/ssg/84/index.html",
"/ssg/85/index.html",
"/ssg/86/index.html",
"/ssg/87/index.html",
"/ssg/88/index.html",
"/ssg/89/index.html",
"/ssg/9/index.html",
"/ssg/90/index.html",
"/ssg/91/index.html",
"/ssg/92/index.html",
"/ssg/93/index.html",
"/ssg/94/index.html",
"/ssg/95/index.html",
"/ssg/96/index.html",
"/ssg/97/index.html",
"/ssg/98/index.html",
"/ssg/99/index.html",
"/counter/",
"/hello/",
"/404/",
"/ssg/1/",
"/ssg/2/",
"/ssg/3/",
"/ssg/4/",
"/ssg/5/",
"/ssg/6/",
"/ssg/7/",
"/ssg/8/",
"/ssg/9/",
"/ssg/10/",
"/ssg/11/",
"/ssg/12/",
"/ssg/13/",
"/ssg/14/",
"/ssg/15/",
"/ssg/16/",
"/ssg/17/",
"/ssg/18/",
"/ssg/19/",
"/ssg/20/",
"/ssg/21/",
"/ssg/22/",
"/ssg/23/",
"/ssg/24/",
"/ssg/25/",
"/ssg/26/",
"/ssg/27/",
"/ssg/28/",
"/ssg/29/",
"/ssg/30/",
"/ssg/31/",
"/ssg/32/",
"/ssg/33/",
"/ssg/34/",
"/ssg/35/",
"/ssg/36/",
"/ssg/37/",
"/ssg/38/",
"/ssg/39/",
"/ssg/40/",
"/ssg/41/",
"/ssg/42/",
"/ssg/43/",
"/ssg/44/",
"/ssg/45/",
"/ssg/46/",
"/ssg/47/",
"/ssg/48/",
"/ssg/49/",
"/ssg/50/",
"/ssg/51/",
"/ssg/52/",
"/ssg/53/",
"/ssg/54/",
"/ssg/55/",
"/ssg/56/",
"/ssg/57/",
"/ssg/58/",
"/ssg/59/",
"/ssg/60/",
"/ssg/61/",
"/ssg/62/",
"/ssg/63/",
"/ssg/64/",
"/ssg/65/",
"/ssg/66/",
"/ssg/67/",
"/ssg/68/",
"/ssg/69/",
"/ssg/70/",
"/ssg/71/",
"/ssg/72/",
"/ssg/73/",
"/ssg/74/",
"/ssg/75/",
"/ssg/76/",
"/ssg/77/",
"/ssg/78/",
"/ssg/79/",
"/ssg/80/",
"/ssg/81/",
"/ssg/82/",
"/ssg/83/",
"/ssg/84/",
"/ssg/85/",
"/ssg/86/",
"/ssg/87/",
"/ssg/88/",
"/ssg/89/",
"/ssg/90/",
"/ssg/91/",
"/ssg/92/",
"/ssg/93/",
"/ssg/94/",
"/ssg/95/",
"/ssg/96/",
"/ssg/97/",
"/ssg/98/",
"/ssg/99/",
"/ssg/100/"
]
}
解決策
Astroはビルドする際にpublic/_route.json
が存在すれば、dist/_route.json
にコピーします。これを利用するため、手動でワイルドカードを用いた_route.json
を定義しましょう。上記の非常に長いJSONから以下のようなスマートなJSONにすることでデプロイを通すことができました。
{
"version": 1,
"include": [
"/*"
],
"exclude": [
"/_worker.js",
"/404.html",
"/404/*",
"/favicon/*",
"/asset/*",
"/_astro/*",
"/counter/*",
"/hello/*",
"/ssg/*"
]
}
_route.jsonを書く際の注意点
極力ワイルドカードで定義するために、以下の点に気をつけましょう。
-
public/
やsrc/pages/
の直下にファイルを置かない
public/
やsrc/pages/
は区別なくルート直下にマッピングされるため、様々なファイルが配置され、ワイルドカードを使った定義が難しくなります(includeの設定を調整することで改善できる可能性あり)。なるべくディレクトリを切って、あるディレクトリ以下は全てSSGという設定にするとルールの数を減らすことができます。上記のJSONではpublic/
以下にfaviconや静的ファイルのディレクトリ(public/favicon/
,public/asset/
)を作って、ルールの数を少なくしています。 - 手動の設定なので実装に合わせてルールを追記する必要がある
viteのプラグイン作成するなど、頑張れば自動化もできそうですが、基本は手動での追加になります。追加を忘れるとデプロイしても反映されない可能性があるので、レビューなどで注意して見る必要があります。
まとめ
思いもよらないところで、つまづきました… でもエラーメッセージでドキュメントが示されていて、丁寧なところが好感持てますね。他のCloudflare Pagesに対応しているフレームワークはどのような_route.json
を生成するのか気になりますね。
備考
すでに議論されているようなので、近いうちに解決されるかもしれません。
Discussion