第2回
最小Reactフレームワーク Waku をCloudflareで動かす
Jotai作者による最小Reactフレームワーク Waku を実際に試す。プロジェクト作成からServer Components、Cloudflareへのデプロイまでを手順を追って動かしながら解説する。
「React最新ラボ」第2回。今回はフレームワークを丸ごと立ち上げて動かす。Server Components(RSC)を一番小さい構成で試し、最後に Cloudflare へデプロイする。
⚠️ Waku は本記事時点で 1.0 alpha。公開APIは安定化に向かっているが、本番採用は段階を踏んで。
そもそも RSC(Server Components)で何が嬉しい?
これまでの作り方とRSCの違いを並べると分かりやすい。
| これまで(CSR) | RSC | |
|---|---|---|
| データ取得 | useEffect + fetch をクライアントで実行 |
コンポーネント自体が async。サーバで取得 |
| クライアントに届くもの | コンポーネントのJS+取得後のデータ | 描画済みのHTML(+必要な所だけJS) |
| 秘密情報 | クライアントに晒さない工夫が要る | サーバ内で完結。鍵やDB接続をそのまま書ける |
プロジェクトを作る
Cloudflare に置く前提なら、Cloudflare のCLI(C3)から作るのが速い。
npm create cloudflare@latest my-waku-app -- --framework=waku対話では次のように選ぶ。
- テンプレート:Framework Starter → Waku
- 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)。ブログのように内容が変わらないページに向く。
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 にデプロイする
// ./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 deploywrangler が Waku を自動検出して必要な設定を生成する(エントリ dist/worker.js、アセット dist/public、互換フラグ nodejs_compat など)。
つまずきやすい所
- サーバとクライアントの境界:
windowやlocalStorageなどブラウザ専用APIはサーバコンポーネントで使えない。使う部分は'use client'側へ移す。 - alpha 版:1.0 alpha なので、バージョンによって細部が変わることがある。詰まったら必ず公式ドキュメントで現行の書き方を確認する。
まとめ
- Waku は Vite + Hono ベースの最小RSCフレームワーク。
src/pagesのファイルベースルーティング - 何も書かなければサーバコンポーネント。
asyncでデータ取得をそのまま書ける - インタラクティブは
'use client'、サーバ処理は'use server'で切り分ける - Cloudflare へは
waku/adapters/cloudflare+npm run deployで完結
次回はシリーズ最終回。見た目の話に戻って、アニメ付きUIコンポーネント集 React Bits を、このサイトと同じ Vite + Tailwind 環境に最短で入れて動かす。