はじめに
React Server Components(RSC)、気になってはいるけれど「Next.jsのApp Routerは覚えることが多すぎる」と感じていませんか?キャッシュ戦略、特殊なファイル規約、独自の拡張API……RSCそのものを理解する前に、フレームワーク固有の知識の壁にぶつかってしまう方は少なくないはずです。
そんな方にこそ試してほしいのが、今回紹介する**Waku(わく)**です。Wakuは「The minimal React framework」を掲げる、必要最小限の機能に絞ったReactフレームワークです。余計な抽象化がないぶん、React 19のサーバーコンポーネントやサーバーアクションといった「Reactの素の姿」を最短距離で学べます。
この記事では、Wakuの特徴からインストール、ルーティングやサーバーアクションの実装まで、コード例を交えて解説します。
Wakuとは
Wakuは、Jotai・Zustandの作者として知られるDaishi Kato氏が中心となって開発している、ミニマル志向のReactフレームワークです。GitHubスター数は6,000を超え、2026年7月時点でv1.0.0のベータ版が精力的にリリースされています。
マーケティングサイトやヘッドレスコマース、フルスタックWebアプリケーションを主なターゲットとしており、「プロジェクトの規模」ではなく「RSCベースのアーキテクチャを採用したいかどうか」で選ぶフレームワークと位置づけられています。
主な特徴
- React 19の最新機能をフルサポート - サーバーコンポーネント、サーバーアクションといったモダンReactの機能を、独自拡張なしのプレーンな形で使えます
- ミニマルなAPI表面積 - フレームワーク側が覚えさせることを最小限に抑え、足りない部分はエコシステムのライブラリと組み合わせる設計思想です
- ファイルベースルーティング -
src/pagesディレクトリの構造がそのままルートになります。ページ単位でSSG(静的生成)とSSR(動的レンダリング)を切り替え可能です - 豊富なデプロイ先 - Node.jsはもちろん、Vercel、Netlify、Cloudflare Workersなどへのアダプターが公式に用意されています
インストール
新規プロジェクトの作成はコマンド1つです。
npm create waku@latest
pnpmやyarnを使う場合は次のとおりです。
# pnpm
pnpm create waku
# yarn
yarn create waku
プロジェクト名を聞かれるので入力すると、テンプレートが生成されます。あとは依存関係をインストールして開発サーバーを起動するだけです。
cd my-waku-app
npm install
npm run dev
http://localhost:3000 にアクセスすれば、もうWakuアプリが動いています。コマンド体系も waku dev(開発)、waku build(ビルド)、waku start(本番実行)の3つだけと、とてもシンプルです。
なお、Node.jsは22.15.0以上が必要です。
基本的な使い方
サーバーコンポーネントとクライアントコンポーネント
Wakuではすべてのコンポーネントがデフォルトでサーバーコンポーネントになります。サーバーコンポーネントはasync関数として定義でき、データベースアクセスやファイル読み込みをコンポーネント内に直接書けます。
// ./src/pages/index.tsx
import { getProducts } from '../lib/db';
import { Gallery } from '../components/gallery';
export default async function HomePage() {
const products = await getProducts();
return (
<main>
<h1>商品一覧</h1>
<Gallery products={products} />
</main>
);
}
export const getConfig = async () => {
return { render: 'dynamic' } as const;
};
ここで使った重い依存関係やDBクライアントは、クライアントに送られるバンドルには一切含まれません。これがRSCの大きなメリットです。
一方、useStateやイベントハンドラなどブラウザ側の機能が必要な場合は、ファイルの先頭に'use client'を付けてクライアントコンポーネントにします。
// ./src/components/counter.tsx
'use client';
import { useState } from 'react';
export const Counter = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>増やす</button>
</div>
);
};
「サーバーが基本、インタラクティブな部分だけクライアント」という切り分けを、Wakuは特別な設定なしにそのまま体験させてくれます。
ファイルベースルーティング
ルーティングはsrc/pagesディレクトリの構造で決まります。
src/pages/
├── _layout.tsx → 全ページ共通レイアウト
├── index.tsx → /
├── about.tsx → /about
└── blog/
└── [slug].tsx → /blog/:slug(動的ルート)
動的ルートでは、パラメータが型安全に受け取れます。
// ./src/pages/blog/[slug].tsx
import type { PageProps } from 'waku/router';
import { getPost, getAllSlugs } from '../../lib/posts';
export default async function BlogArticlePage({
slug,
}: PageProps<'/blog/[slug]'>) {
const post = await getPost(slug);
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}
export const getConfig = async () => {
return {
render: 'static',
staticPaths: await getAllSlugs(),
} as const;
};
注目してほしいのがgetConfigです。render: 'static'を返せばビルド時に静的生成(SSG)、render: 'dynamic'を返せばリクエストごとのサーバーレンダリング(SSR)になります。ページ単位でSSGとSSRを宣言的に切り替えられるのは、Wakuの気持ちいいところです。
ページ間のナビゲーション
内部リンクにはLinkコンポーネントを使います。
import { Link } from 'waku';
export default async function HomePage() {
return (
<nav>
<Link to="/about">このサイトについて</Link>
</nav>
);
}
クライアントコンポーネントからプログラム的に遷移したい場合はuseRouterフックが使えます。
'use client';
import { useRouter } from 'waku';
export const BackButton = () => {
const router = useRouter();
return <button onClick={() => router.push('/')}>トップへ戻る</button>;
};
実践的なユースケース
サーバーアクションでお問い合わせフォームを作る
RSCの目玉機能のひとつがサーバーアクションです。'use server'を付けた関数は、クライアントから呼び出せる安全なサーバー処理になります。API層を自前で書かずに、フォーム送信を実装してみましょう。
// ./src/pages/contact.tsx
import { saveMessage } from '../lib/db';
import { ContactForm } from '../components/contact-form';
export default async function ContactPage() {
const sendMessage = async (formData: FormData) => {
'use server';
const message = formData.get('message');
if (typeof message !== 'string' || message.length === 0) {
return;
}
await saveMessage(message.slice(0, 1000));
};
return (
<main>
<h1>お問い合わせ</h1>
<ContactForm sendMessage={sendMessage} />
</main>
);
}
export const getConfig = async () => {
return { render: 'dynamic' } as const;
};
// ./src/components/contact-form.tsx
'use client';
export const ContactForm = ({
sendMessage,
}: {
sendMessage: (formData: FormData) => Promise<void>;
}) => {
return (
<form action={sendMessage}>
<textarea name="message" required maxLength={1000} />
<button type="submit">送信</button>
</form>
);
};
サーバー側の関数をpropsとして渡し、フォームのactionにそのまま接続する——この書き心地は、一度体験すると従来のAPI設計に戻れなくなるかもしれません。
APIエンドポイントを生やす
外部サービスからのWebhook受信など、コンポーネントと切り離したエンドポイントが必要な場合は、src/pages/_apiディレクトリにファイルを置くだけです。
// ./src/pages/_api/health.ts
export const GET = async (): Promise<Response> => {
return Response.json({ status: 'ok' }, { status: 200 });
};
Web標準のRequest/Responseをそのまま使うので、フレームワーク固有の知識はほぼ不要です。
デプロイ
ビルドしたアプリはNode.jsサーバーとしてそのまま動くほか、Vercel、Netlify、Cloudflare Workersなどへのデプロイにも公式アダプターで対応しています。静的生成のみのサイトであれば、ビルド成果物を静的ホスティングに置くだけでも運用できます。
まとめ
Wakuの魅力を振り返ってみましょう。
- 最小限のAPIで、React Server Componentsとサーバーアクションを素に近い形で学べます
getConfigによってページ単位でSSG/SSRを切り替えられます- サーバーアクションやAPIエンドポイントなど、小規模なフルスタックアプリに必要な道具が一通り揃っています
多機能なNext.jsやRemixが「全部入りの重装備」だとすれば、Wakuは「必要なものだけを持った身軽な装備」です。RSCという新しいパラダイムを学ぶ教材としても、マーケティングサイトや小〜中規模アプリの実戦投入先としても、有力な選択肢になるはずです。
npm create waku@latest の一行から始められるので、まずは週末のプロジェクトで「わくわく」する開発体験を味わってみてはいかがでしょうか。
