Next.js is powerful but heavy. Remix merged into React Router. Waku is the minimal React framework built from the ground up for React Server Components (RSC).
Why Waku
- RSC-first — designed around Server Components, not retrofitted
- Minimal — no opinions about styling, state, or data fetching
- Fast — built on Vite, instant HMR
- Small — ~3KB client runtime
Getting Started
npm create waku@latest my-app
cd my-app && npm run dev
Server Components (Default)
// src/pages/index.tsx (Server Component — runs on server)
import { getProducts } from "../lib/db";
export default async function HomePage() {
const products = await getProducts(); // Direct DB access
return (
<main>
<h1>Products</h1>
{products.map(p => (
<article key={p.id}>
<h2>{p.name}</h2>
<p>${p.price}</p>
<AddToCartButton productId={p.id} />
</article>
))}
</main>
);
}
No useEffect. No loading states. No API routes. The component fetches data on the server and streams HTML to the client.
Client Components (Opt-In)
// src/components/AddToCartButton.tsx
"use client";
import { useState } from "react";
export function AddToCartButton({ productId }: { productId: string }) {
const [added, setAdded] = useState(false);
return (
<button onClick={() => {
addToCart(productId);
setAdded(true);
}}>
{added ? "Added!" : "Add to Cart"}
</button>
);
}
Only interactive components ship JavaScript to the browser.
Server Actions
// src/actions/cart.ts
"use server";
export async function addToCart(productId: string) {
const session = await getSession();
await db.cart.add({ userId: session.userId, productId });
revalidate("/cart");
}
// Used in client component
import { addToCart } from "../actions/cart";
<form action={addToCart}>
<input type="hidden" name="productId" value={product.id} />
<button type="submit">Add to Cart</button>
</form>
File-Based Routing
src/pages/
index.tsx → /
about.tsx → /about
products/
index.tsx → /products
[id].tsx → /products/:id
_layout.tsx → Shared layout
Layouts
// src/pages/_layout.tsx
import { Header } from "../components/Header";
import { Footer } from "../components/Footer";
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<Header />
<main>{children}</main>
<Footer />
</body>
</html>
);
}
Waku vs Next.js vs Remix
| Next.js | React Router v7 | Waku | |
|---|---|---|---|
| RSC support | Yes (App Router) | No | Yes (native) |
| Complexity | High | Medium | Low |
| Bundler | Webpack/Turbopack | Vite | Vite |
| Client runtime | ~85KB | ~70KB | ~3KB |
| Opinionated | Very | Moderate | Minimal |
| Use case | Full-stack apps | SPAs + SSR | RSC-first apps |
Deploy
Waku supports: Vercel, Netlify, Cloudflare, Deno, AWS Lambda, Node.js.
npm run build
# Output adapts to your deployment target
Need modern React architecture? I build web tools and data solutions. Email spinov001@gmail.com or explore my Apify tools.
Top comments (0)