DEV Community

Alex Spinov
Alex Spinov

Posted on

Waku Has a Free Minimal React Framework Built for React Server Components

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
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode
// 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>
Enter fullscreen mode Exit fullscreen mode

File-Based Routing

src/pages/
  index.tsx          → /
  about.tsx          → /about
  products/
    index.tsx        → /products
    [id].tsx         → /products/:id
  _layout.tsx        → Shared layout
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Need modern React architecture? I build web tools and data solutions. Email spinov001@gmail.com or explore my Apify tools.

Top comments (0)