仕組み
@k8ordo/staticと@k8ordo/serverがどう動いているかを説明します。使い方を覚えるのに必要な内容ではありませんが、ビルドがなぜそのファイルを断るのかが分かると、迷ったときに判断しやすくなります。
このページの内容
モードは依存で決まる
アプリをファイルに書き出すか、サーバーで動かすかは、設定の値ではなく、どちらのパッケージをインストールしたかで決まります。@k8ordo/staticで作ったアプリからは、サーバーの仕組みにそもそも手が届きません。
1つのパッケージのオプションにしなかったのは、このためです。静的なアプリでリクエストを読まないことは、守るべき決まりではありません。読む先のリクエストが、そもそも無いからです。
それ以外は何も変わりません。ルートの書き方も、サーバーとブラウザの境界も、リクエストに答えるハンドラも同じです。プラグインの名前がどちらもframework()なのはそのためで、vite.config.tsはどちらのモードでも同じ形になります。
同じハンドラを、いつ呼ぶかだけが違う
どちらのモードでも、vite buildはdist/rsc/index.jsにリクエストハンドラを書き出します。Requestを受け取ってResponseを返す、ただの関数です。2つのモードの違いは、このハンドラをいつ呼ぶかにあります。
@k8ordo/static
vite build → handler(/products/1) → index.html
→ handler(/products/1/index.rsc) → index.rsc
@k8ordo/server
request → handler(request) → Response@k8ordo/staticは、ビルドの最後にページごとにハンドラを2回呼びます。HTMLの答えをindex.htmlに、ペイロードの答えをindex.rscに書き、ファイルとして残します。
@k8ordo/serverは、リクエストが届くたびにハンドラを呼び、その答えをそのまま返します。
vite devは、どちらのモードでもリクエストのたびにハンドラを呼びます。そのため@k8ordo/staticのアプリでは、開発中とビルドで振る舞いが違うところがあります。
ページはHTMLとペイロードの2つの形を持つ
どちらのモードでも、ページにはHTMLのほかに、RSCのペイロードという形があります。ペイロードはページのパスの後ろに/index.rscを付けた場所にあり、クライアント側の遷移ではこれを取りに行きます。
ヘッダーやクエリではなくパスで分けているのは、静的ホスティングがそのどちらでも答えを変えないからです。パスなら、ファイルの集まりでも動いているサーバーでも、同じ決まりで答えられます。
最初に開いたページでは、描画に使ったペイロードがHTMLに埋め込まれて届きます。hydrationはそれを読むので、同じページを2回取りに行くことはありません。
@k8ordo/staticが断るもの
ファイルを書き出すビルドには、リクエストがありません。そのため、リクエストを必要とするものはビルドが名指しで断ります。
'use server'のモジュール:ファイルはフォームの送信を受け取れません。guard.ts:ファイルには、通すかどうかを決めるリクエストがありません。GET以外のメソッドをexportするroute.ts:ファイルはGETにしか答えられません。searchをexportするページ:ファイルの中身は、searchが何であっても同じです。
vite devは動いているサーバーなので、本来ならこれらも受け付けられます。しかし、開発中は動くのに本番ではどこにも届かないフォームは、最初から動かないフォームよりも困ります。そこでvite devも、そのファイルを読み込んだ時点で同じように断ります。
どの断り方も、this application wants @k8ordo/serverという行で終わります。どれかが必要なら、@k8ordo/serverに移ってください。ほかのコードはそのまま使えます。
パラメータを持つルートの値が分からないときも、ビルドは止まります。警告を出して飛ばすことはしません。ページが半分欠けたサイトを黙って公開するより、止まるほうが安全だからです。
@k8ordo/serverが断るもの
動いているサーバーには、どこからでもリクエストが届きます。そのためハンドラは、アプリが答えるべきでないリクエストを、何も描かないうちに断ります。
- 別のoriginからの
POST:route.ts以外へのPOSTで、Originヘッダーが無いか、そのホストが一致しないものには403で答えます。別のサイトのフォームに、訪問者のCookieを付けたままアクションを呼ばせないためです。 GETとHEAD、POST以外のメソッド:ページへのリクエストなら、その3つをAllowに並べた405で答えます。- Viteの
baseの外のURL:アプリのものではないので、404で答えます。
どちらのモードでも断るもの
フレームワークの規約は、覚えておく習慣ではなく、検査できる形にしてあります。URLがどこにあり、サーバーとブラウザの境界がどこにあるかを、読み手の記憶ではなくフレームワークが確かめるためです。次のものは、どちらのモードでもエラーになります。
routes/の決まりに合わないファイルやディレクトリと、決して描かれないルート- 非同期に検証する
paramsSchema - クライアントのバンドルに入りそうになった、
server-onlyのモジュール - ルートから始まるパスではない、Viteの
base