Get Started
@k8ordo/static を入れて vite.config.ts にプラグインを 1 つ足し、routes/ にレイアウトとページを置けば、vite build がサイトをファイルに書き出します。このページはそこまでの最短の道筋と、このモードを選ぶことが何を意味するかを説明します。
モードは依存で決まる
このパッケージを入れることが、アプリを静的にすることです。ここでのリクエスト時のデータは、覚えておくべき規則ではありません。リクエストそのものが無いので、読む先がありません。
RSC のパイプラインがそれでもコンパイルしてしまう唯一のものが Server Action です。どこにも届かない POST をするフォームを出荷しないよう、ビルドは 'use server' を宣言したモジュールをすべて名指しで拒みます。
static build cannot ship Server Actions — a file cannot receive one, and these declare 'use server':
src/routes/_parts/guestbook.ts
this application wants @k8ordo/servervite dev はその POST を受け取れてしまう実行中のサーバーなので、同じ拒否は、そのファイルがコンパイルされた時点で dev でも出ます。開発では動くのに本番ではどこにも届かないフォームは、最初から動かないフォームより悪いからです。
もう一方のモードを選ぶことは、代わりに @k8ordo/server を入れることで、アプリのほかの部分は何も変わりません。ルートの文法も、境界も、リクエストハンドラも同じで、ハンドラがビルド時にルートごとに呼ばれるか、リクエストごとに呼ばれるかだけが違います。プラグインが両方のパッケージで framework() という名前なのはそのためで、モードを決めるのは import だけです。vite.config.ts はどちらでも同じに読めます。
インストール
ルーターと React は実行時の依存です。@k8ordo/static と Vite はビルドのときにしか使わないので、開発時の依存に入れます。server-only は、サーバー専用のモジュールに付ける import を TypeScript が解決できるようにするためのものです。
npm install @k8ordo/router react react-dom server-only
npm install -D @k8ordo/static viteピア依存と、ビルドを動かす Node.js の要件です。
- @k8ordo/router
- React >= 19.3.0
- React DOM >= 19.3.0
- Vite >= 8.2.1
- Node.js >= 24
vite.config.ts
プラグインは framework() 1 つです。オプションは 3 つあり、routesDir はルートのディレクトリ(既定は src/routes)、paths はパラメータ付きルートの pathname、site はサイトの配信元で、渡すとビルドが sitemap.xml も書きます。
// vite.config.ts
import { framework } from '@k8ordo/static';
import { defineConfig } from 'vite';
export default defineConfig({ plugins: [framework()] });framework() は Vite のプラグインの配列を返し、その中に React のプラグイン(Fast Refresh)と RSC のパイプラインがすでに入っています。@vitejs/plugin-react を自分で足す必要はありません。
tsconfig.json
型の配線を効かせるには、tsconfig.json の include に .k8ordo をグロブで書きます。.k8ordo はドットで始まるので、ディレクトリ名だけを書いた include は黙ってそれを飛ばします。飛ばされてもビルドは通り、href が表に対して検査されなくなるだけです。
{
"include": ["src/**/*.ts", "src/**/*.tsx", ".k8ordo/**/*.ts"]
}最小の routes/
ルートレイアウトが <html> と <body> を描きます。フレームワークは文書のテンプレートを持ちません。見えないテンプレートは、変えられないテンプレートだからです。ディレクティブの無いファイルは Server Component です。
// src/routes/layout.tsx
import type { ReactNode } from 'react';
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}// src/routes/page.tsx
export default function HomePage() {
return (
<>
<title>home</title>
<h1>hello</h1>
</>
);
}ルートレイアウトは文書そのもの
hydration は文書の全体を照合します。フレームワークが書いた HTML とブラウザのあいだで、HTML を書き換えるもの、たとえば Web フォントをインライン化したり、メールアドレスを難読化したり、スクリプトを遅延させたりする CDN は、React が照合しようとしている木を変えてしまい、その差分で hydration が失敗します。影響を受けない方法は、そうしたサービスに書き換える余地を与えないことです。アセットは別のオリジンから読まず、同じオリジンから配信します。
動かす
vite dev はリクエストごとに描画するサーバーで、Fast Refresh も効きます。vite build は全ルートを描いて dist/client/ に書き、そのディレクトリがサイトです。
vite dev
vite buildvite dev はビルドと同じハンドラを動かしますが、ファイルは書かないので、次の点は本番と違います。paths に無いパラメータの値もそのまま描かれます。redirect.ts はリダイレクトのページではなく 307(permanent なら 308)で答えます。知らない URL には、ホスティングの 404.html ではなく、ハンドラが 404 で答えます(not-found.tsx があればそれを描きます)。Server Component が throw したページは、error.tsx ではなく、throw されたメッセージを載せた 500 で答えます。ビルドならそこで止まるところです。
ビルドは最後に、書いたものを 1 行で報告します。
k8ordo: wrote 1 routes生成されるもの
最初の vite dev か vite build が .k8ordo/ を書きます。ルート表(routes.gen.ts)、それを @k8ordo/router に結ぶ型の配線(register.gen.ts)、そして自分自身を git から外す .gitignore です。
vite build は型を検査しません。satisfies による検査の結果を出すのは tsc です。.k8ordo/ は git に入らないので、新しいチェックアウトでは tsc の前に一度 vite dev か vite build を走らせて書かせます。
中身と、ビルドが拒むものは、リンク先にあります。 routes/
@k8ordo/server を選ぶとき
リクエストを必要とするものは、このモードにはありません。次のどれかが要るなら、アプリが求めているのは @k8ordo/server です。このページの内容はそのまま使えます。
- フォームの送信先になる Server Action と、そこからの
redirect() - ページがリクエストのヘッダーや cookie を読むこと
- アプリが決めるステータスコード(知らない URL への本物の 404 を含む)
- 前もって列挙できないパラメータの値