第2回

最小Reactフレームワーク Waku をCloudflareで動かす

Jotai作者による最小Reactフレームワーク Waku を実際に試す。プロジェクト作成からServer Components、Cloudflareへのデプロイまでを手順を追って動かしながら解説する。

·15分で読める
たける
たける Server Components を試したいんですけど、Next.js はいきなり大きくて設定も多くて。もっと小さく「RSCだけ」を触れる方法ないですか?できればこのサイトみたいに Cloudflare に置きたいです。
りこ
りこ それなら **Waku** がいい。Jotai や Zustand を作った Daishi Kato さんの「最小Reactフレームワーク」。Vite と Hono の上に乗っていて、React 19 の Server Components / Server Functions をそのまま使える。Cloudflare 用のアダプタもある。

「React最新ラボ」第2回。今回はフレームワークを丸ごと立ち上げて動かす。Server Components(RSC)を一番小さい構成で試し、最後に Cloudflare へデプロイする。

⚠️ Waku は本記事時点で 1.0 alpha。公開APIは安定化に向かっているが、本番採用は段階を踏んで。

そもそも RSC(Server Components)で何が嬉しい?

ユナ
ユナ ざっくり言うと「コンポーネントをサーバ側でレンダリングして、クライアントに送るJavaScriptを減らす」仕組み。コンポーネントの中で直接DBやファイルにアクセスできる。

これまでの作り方とRSCの違いを並べると分かりやすい。

これまで(CSR) RSC
データ取得 useEffect + fetch をクライアントで実行 コンポーネント自体が async。サーバで取得
クライアントに届くもの コンポーネントのJS+取得後のデータ 描画済みのHTML(+必要な所だけJS)
秘密情報 クライアントに晒さない工夫が要る サーバ内で完結。鍵やDB接続をそのまま書ける
サーバでレンダ
HTMLを送信
必要な所だけJS

プロジェクトを作る

Cloudflare に置く前提なら、Cloudflare のCLI(C3)から作るのが速い。

npm create cloudflare@latest my-waku-app -- --framework=waku

対話では次のように選ぶ。

  • テンプレート:Framework StarterWaku
  • git で管理:Yes
  • すぐデプロイ:No(まずローカルで動かす)

Cloudflare 抜きで純粋に Waku だけ試すなら npm create waku@latest でもいい。

作ったら開発サーバを起動する。

cd my-waku-app
npm run dev
# → http://localhost:3000

ファイル構成:src/pages がそのままルートになる

Waku は src/pages ディレクトリのファイルベースルーティング。ファイルを置けば、そのままURLになる。

src/
├── pages/
│   ├── _layout.tsx     # 全ページ共通のレイアウト
│   ├── index.tsx       # /
│   └── about.tsx       # /about
├── components/
└── waku.server.ts      # サーバ/ルーティング設定(後述)

各ページは「default で React コンポーネントを export」し、必要に応じて「getConfig という名前付き関数を export」してレンダリング方法を指定する。

サーバコンポーネントを書く(デフォルトでサーバ実行)

src/pages 以下のコンポーネントは、何も書かなければサーバコンポーネントasync にできて、中で直接 fetch やDBアクセスを書ける。

// ./src/pages/index.tsx
export default async function HomePage() {
  // ここはサーバで動く。fetch も DB も直接書ける
  const res = await fetch('https://api.github.com/repos/wakujs/waku')
  const repo = await res.json()

  return (
    <main>
      <h1>Waku を試す</h1>
      <p>GitHub ★ {repo.stargazers_count}</p>
    </main>
  )
}

// レンダリング方法を指定(dynamic = リクエストごとにサーバでレンダ)
export const getConfig = async () => {
  return { render: 'dynamic' } as const
}

render: 'static' にすれば、ビルド時にHTMLを生成する(静的化/SSG)。ブログのように内容が変わらないページに向く。

ユナ
ユナ `useEffect` も、ローディング状態の管理もない。「サーバで取る → 描画済みで届く」だけ。データ取得の定番パターンがまるごと消えるのが体感ポイント。
/ にアクセスしたときの表示(サーバで取得済みのHTMLが届く)

Waku を試す

GitHub ★ 13200

インタラクティブな所だけ 'use client'

クリックや useState が必要な部分は、クライアントコンポーネントに切り出す。ファイルの先頭に 'use client' を書くだけ。

// ./src/components/counter.tsx
'use client'

import { useState } from 'react'

export const Counter = () => {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount((c) => c + 1)}>カウント: {count}</button>
}

これをサーバコンポーネントから普通に import して置けばいい。「ページ全体はサーバ、ボタンだけクライアント」という構成になる。

サーバ処理を呼ぶ:'use server'(Server Functions)

フォーム送信やDB書き込みなど「サーバでだけ動かしたい処理」は Server Function にする。専用ファイルの先頭に 'use server' を置く。

// ./src/actions/send-message.ts
'use server'

export async function sendMessage(message: string) {
  // ここはサーバでだけ実行される。DB接続などをここに書く
  console.log('received:', message)
}
// ./src/components/contact-button.tsx
'use client'

import { sendMessage } from '../actions/send-message'

export const ContactButton = () => {
  return <button onClick={() => sendMessage('Hello!')}>送信</button>
}

'use server' のファイルから export した関数は、そのままサーバのエンドポイントになる。クライアント側からは「ただの関数呼び出し」に見えるのに、中身はサーバで動く。

Cloudflare にデプロイする

翔太
翔太 Cloudflare 向けは「アダプタ」を設定するだけ。`src/waku.server.ts` で Cloudflare アダプタと `fsRouter` を組み合わせる。C3 の Waku スターターなら最初から入ってる。
// ./src/waku.server.ts
import { fsRouter } from 'waku'
import adapter from 'waku/adapters/cloudflare'

export default adapter(
  fsRouter(import.meta.glob('./**/*.{tsx,ts}', { base: './pages' })),
)

あとはデプロイコマンド一発。

npm run deploy

wrangler が Waku を自動検出して必要な設定を生成する(エントリ dist/worker.js、アセット dist/public、互換フラグ nodejs_compat など)。

翔太
翔太 このサイト自体は Cloudflare Pages。Waku は Workers 側だけど、同じ Cloudflare の上。普段の延長で試せるのがいい。
たける
たける Next.js を入れずに、Server Components とデプロイまで一通り触れました。これくらい小さいと「RSCって何が起きてるか」が掴みやすいですね。

つまずきやすい所

  • サーバとクライアントの境界windowlocalStorage などブラウザ専用APIはサーバコンポーネントで使えない。使う部分は 'use client' 側へ移す。
  • alpha 版:1.0 alpha なので、バージョンによって細部が変わることがある。詰まったら必ず公式ドキュメントで現行の書き方を確認する。

まとめ

  • Waku は Vite + Hono ベースの最小RSCフレームワークsrc/pages のファイルベースルーティング
  • 何も書かなければサーバコンポーネント。async でデータ取得をそのまま書ける
  • インタラクティブは 'use client'、サーバ処理は 'use server' で切り分ける
  • Cloudflare へは waku/adapters/cloudflarenpm run deploy で完結

次回はシリーズ最終回。見た目の話に戻って、アニメ付きUIコンポーネント集 React Bits を、このサイトと同じ Vite + Tailwind 環境に最短で入れて動かす。