@k8ordo/static

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/server

vite 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.jsoninclude.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 build

vite dev はビルドと同じハンドラを動かしますが、ファイルは書かないので、次の点は本番と違います。paths に無いパラメータの値もそのまま描かれます。redirect.ts はリダイレクトのページではなく 307permanent なら 308)で答えます。知らない URL には、ホスティングの 404.html ではなく、ハンドラが 404 で答えます(not-found.tsx があればそれを描きます)。Server Component が throw したページは、error.tsx ではなく、throw されたメッセージを載せた 500 で答えます。ビルドならそこで止まるところです。

ビルドは最後に、書いたものを 1 行で報告します。

k8ordo: wrote 1 routes

生成されるもの

最初の vite devvite build.k8ordo/ を書きます。ルート表(routes.gen.ts)、それを @k8ordo/router に結ぶ型の配線(register.gen.ts)、そして自分自身を git から外す .gitignore です。

vite build は型を検査しません。satisfies による検査の結果を出すのは tsc です。.k8ordo/ は git に入らないので、新しいチェックアウトでは tsc の前に一度 vite devvite build を走らせて書かせます。

中身と、ビルドが拒むものは、リンク先にあります。 routes/

@k8ordo/server を選ぶとき

リクエストを必要とするものは、このモードにはありません。次のどれかが要るなら、アプリが求めているのは @k8ordo/server です。このページの内容はそのまま使えます。

  • フォームの送信先になる Server Action と、そこからの redirect()
  • ページがリクエストのヘッダーや cookie を読むこと
  • アプリが決めるステータスコード(知らない URL への本物の 404 を含む)
  • 前もって列挙できないパラメータの値

@k8ordo/server

次のステップ