@cloudflare/next-on-pages 読む
- fetch の next option も動く
- fetch の cache はデフォルトだと CacheAPI に置かれる
- cacheName は
suspense-cache
- KV にも変更できる
-
__NEXT_ON_PAGES__KV_SUSPENSE_CACHE
という namespace で KV を binding すると KV が優先される
-
- cache の保存、パージは Next.js 側が管理してる
- CacheAdapter は Next.js からやってくる internal request を見て保存したり、破棄するだけ
- cacheName は
- KV と Cache API どっちがいいの?
- メンテナ的には KV らしい Cache API はリージョンごとに内容が変わるから
- next/image は動かない
- Cloudflare Images Transform Images を使おう
- Adaptive Image Format をやりたいなら Cloudflare Worker + Routes で <domain>/images/ 以下を最適化 path に設定するのがよい
-
https://developers.cloudflare.com/images/transform-images/
-
https://zenn.dev/naporin24690/articles/88471777071bb1
- これの実装を transform images に変える感じ
-
https://zenn.dev/naporin24690/articles/88471777071bb1
- これを楽に実装するために hono/cache に Vary options 入れたのでつかって
-
https://developers.cloudflare.com/images/transform-images/
- Adaptive Image Format をやりたいなら Cloudflare Worker + Routes で <domain>/images/ 以下を最適化 path に設定するのがよい
- Cloudflare Images Transform Images を使おう
- middleware は動く
メンテナのデモ
Cloudflare Pages が wrangler.toml を読んでくれるようになったし、本格的に @cloudflare/next-on-pages
が何をしているのか見る。
Build Output Configuration 対応表
next.config.mjs Properties 対応表
少し前に試して動かなかった middleware は今動くようになっているらしい
コードを見て重点的に確認すること
- fetch
- revalidate
- tags
- revalidateTag
- cache
- Cache API なのか KV なのか
- images
- custom loader を cloudflare stack でやるとしてどういう選択肢があるのか
@cloudflare/next-on-pages
は CloudflarePages で動くように成果物を生成するので advanced mode を利用している。そのためすべてのリクエストはまず _worker.js
で処理されるためそこを見ていく。
fetch
Next.js も同じだけどまず fetch に patch を当てるところから始まる。
blob:
や https://INTERNAL_SUSPENSE_CACHE_HOSTNAME.local/v1/suspense-cache/
から始まる request 以外は globalFetch
に流している。
さらっと見てる感じデフォルトの fetch では vercel で cache してくれる next の option に関しては検証してなさそう。
fetch('xxxx', {
next: {
revalidate: 3600,
tags: ['next']
}
blob: から始まる URLについて
コメントには 例えば @vercel/og
に使われると書いてあるが、 build 成果物として .bin
になるようなケースって存在するんだろうか?
wasm を import するとインライン展開されて blob://xxx.wasm
とかになるんかな....?(要調査
plugins が .bin
で展開されているのかも
INTERNAL_SUSPENSE_CACHE_HOSTNAME.local が含まれる URL について
RSC のキャッシュなんだと思うが、Suspense ってでかめの名前を付けているので Streaming されるコンポーネントがすべてキャッシュされそう。
GET は cache があれば返す。なければ 404, POST は revalidateTag を付けたうえで body を cache する。
/v1/suspense-cache/revalidate
に対してリクエストがあったときは method にかかわらず searchParameter の tags をみて revalida する。
@cloudflare/next-on-pages
における SuspenseCache について
SuspenseCache は KV, Cache API がコード中には出てくるが Interface はすべて CacheAdapter
に従っているのでここだけ見ておけばよい。
ここでは cache purge の仕組みは tags と revalidate のみで管理されている。(revalidate は使われてない?)
KV と Cache API の update method をそれぞれ乗せておく。KV は revalidate 見てないので自動で揮発しないらしいがこれはいいのか?
@cloudflare/next-on-pages
で revalidate を検索した
cache purge のタイミング制御には使われていなさそう
images
next/image
を使用すると custom loader の設定をしていなければ /_next/image
以下から画像が配信される。vercel では /_next/images
以下で画像にアクセスするときには squoosh が仕込まれていてリサイズがされるが @cloudflare/next-on-pages
ではどうなるだろうか?
ここで行われている
それぞれ見ていく
- handleImageResizingRequest
- env.ASSETS
- BUILD_OUTPUT
- CONFIG.images
handleImageResizingRequest
url から resize option と切り出したり Accept
header から format を取り出している
自分自身だったら env.ASSETS.fetch
を使ってそうでないなら fetch
を利用
next.config.mjs
から response header を付けてあげる
env.ASSETS
CloudflarePages 専用の service binding。自分自身から静的アセットを取り出すときに使う。
BUILD_OUTPUT
build ディレクトリのファイルを見れるものだと思う
CONFIG.images
next.config.mjs
の中身だと思う
リサイズも画像の format も変わってないじゃん!
せっかくデータとして持ってるのに使ってない回
素直に custom loader と CloudflareImages の Image Transform 単体か ImageTransform + Cloudflare Worker + WorkerRoute を使用しましょうね案件でした。
handleRequest を見ていく
読むのにめちゃくちゃ時間がかかる上に Next.js が吐き出すファイル構造まで知ってなきゃいけないので地力はあきらめた。
handleRequest
の内部では RoutesMatcher
findMatch
generateResponse
関数が実行されていて、 findMatch
は matcher から今回使用する route を決定するやつ、generateResponse
は route を Response に変換する関数。RoutesMatcher
がかなり巨大なのと build した後のファイル構造も詳しく知っている必要があったためざっくり挙動を GPT-4 に聞いた。
このコードは、Cloudflare Pages上でNext.jsのAppRouteを動かすために使用される`@cloudflare/next-on-pages`ランタイムの実装部分です。主に、リクエストが来た時にどのルートにマッチするかを判断し、対応するミドルウェアを実行したり、適切なレスポンスを生成する流れを定義しています。ここでは、その処理の流れとRSCに関する言及を説明します。
### 処理の流れ
1. **コンストラクタ (`constructor`):**
- リクエストのURL、クッキー、パスなどの初期化。
- ワイルドカードマッチの設定。
- ロケール情報のセットアップ。
2. **ルートのマッチング (`checkRouteMatch`):**
- リクエストのパスとルートの`src`属性をPCRE(Perl Compatible Regular Expressions)でマッチング。
- HTTPメソッド、`has`、`missing`、ステータスコードの条件を満たすかチェック。
- マッチした場合、そのルート情報を返す。
3. **ミドルウェアの実行 (`runRouteMiddleware`):**
- 指定されたパスに対応するミドルウェアがあれば実行。
- ミドルウェアからのレスポンスを処理し、必要に応じてリクエストヘッダーやパスを上書き。
4. **ルートの適用 (`applyRouteOverrides`, `applyLocaleRedirects`, `applyRouteHeaders`, `applyRouteStatus`, `applyRouteDest`):**
- ルートに定義されたオーバーライド、リダイレクト、ヘッダー、ステータスコード、デスティネーションの適用。
5. **フェーズのチェック (`checkPhase`):**
- `none`、`miss`、`hit`、`error`といった異なるフェーズでルートのマッチングを試みる。
- 各フェーズでルートを順番にチェックし、マッチするものがあれば処理を実行。
- ルートの処理結果に基づき、次のフェーズへ移動するか、処理を終了する。
6. **マッチングの実行 (`run`):**
- 指定されたフェーズからマッチング処理を開始し、結果を返す。
### RSC (React Server Components) について
RSCは、サーバー側でのみ実行されるReactコンポーネントで、クライアントサイドでのJavaScriptの実行を減らすことでパフォーマンスを向上させることができます。このコード内では、`.rsc`の拡張子を持つパスに特別な扱いをしている部分があります。特に、`applyRouteDest`メソッド内で、リクエストのパスがRSC関連のものであるかどうかをチェックし、処理を調整しています。
- **RSC Indexルートの特別な扱い:** `/index.rsc`への書き換えを試みますが、元のパスが`/`(ルート)以外の場合や、`/__index.prefetch.rsc`へのリクエストの場合は、その書き換えを行いません。
- **RSCファイルがビルド出力に存在しない場合の処理:** パスに`.rsc`拡張子が含まれるが、該当するR
SCページがビルド出力に存在しない場合、その拡張子を削除します。
これらの処理により、RSCに関する特別なケースを適切に扱いつつ、リクエストを正確にルーティングできるようにしています。
request path に対応するファイルを next build
が吐き出すディレクトリにマッチさせて middleware, RSC, static file を実行して Response を返しているということくらいしかわからなかった。
ただ cache 周りが書いてないことだけは分かった。
じゃあ fetch に対して patch を当てていたあのコードたちはどういうことなんだろうか?
Suspense Cache をCacheAPI で使えるようにする PR
Suspense Cache を KV でも使えるようにする PRCacheAPI のほうの PR を読んでいると export const SUSPENSE_CACHE_URL = 'INTERNAL_SUSPENSE_CACHE_HOSTNAME';
は Next.js で使われている環境変数であるといわれている。
Next.js 側から RSC を fetch するときはこの hostname が刺されるってことか
Next.js のリポジトリで SUSPENSE_CACHE_URL
を検索する
おっ?FetchCache.isAcaivable
が true を返すようになるぽいぞ
使ってるところを見てみる
こういうことか........
Next.js は fetch を patch している
多分これ
Next.js から fetch をたたくと
- fetch (Next.js
- staticGenerationStore 経由で IncrementalCache がたたかれる (Next.js
- FetchCache (Next.js
- handleSuspenseCacheRequest (@cloudflare/next-on-pages
staticGenerationStore
は AsyncLocalStorage
か fetch.__nextGetStaticStore()
から刺されるのだが、呼ばれているファイルが複数だったり AsyncLocalStorage
が wrap されたもののあったりして完全に追えなかった。
キーワード
- staticGenerationAsyncStorage
- staticGenerationAsyncStorageWrapper
- requestAsyncStorage
- RequestAsyncStorageWrapper
- __nextGetStaticStore
あれ、revalidateTag
が KV だと動かないの?なんで?
何もわからんくなってきた
patch-package
で挙動を確認しながらいつ cache が入っているか見てみる
cache が使われるときと保存されるタイミングに一貫性がなくて困惑....
Cache API を使用しているときはたまに使われる(Cloudflare の Cache API ってこんなに cache.match
不安定じゃないよな....
外部リクエストには fetch(url, { next: { } })
で cache されるし、revalidateTag
revalidatePath
が動く。
内部リクエストに関しては unstable_cache
がないと動かない。
@cloudflare/next-on-pages
の fetchPatch は効いていたので、next.js 側の fetchPatch がはがれていそうな雰囲気。
suspense-cache
のリクエストが発火してなかったのでかなり怪しい
fetch
-> IncrementalCache
-> FetchCache
-> handleSuspenseCacheRequest
next.js の patch が効いてないと fetch から FetchCache までたどり着かないのでそれで動かないんじゃないかなぁと予想してる
cache が使われるときと保存されるタイミングに一貫性がなくて困惑....
Cache API を使用しているときはたまに使われる(Cloudflare の Cache API ってこんなに cache.match 不安定じゃないよな...
これがなぜかわかった。一度 revalidataTag
をするとすべての fetch が cache されなくなり、deploy するたびに cache されるという挙動をしていたんだが、これは inmemory で revalidatedTags
という revalidateTag
されたときに登録する変数がいるのだが、こいつの状態がクリアされたり、リクエストが来なくなると worker インスタンスが消えて inmemory 状態が揮発するからだった
revalidatedTags
が破棄されない理由も、KV の put が終わらないからという奇妙な理由だった。
getRequestContext
から得られる executeCtx.waitUntil
をかませることで put を待たないようしたら動作するように見えたが、waitUntil
すると cache が更新される前に rerender されることがありなそうなので要調査