TL;DR
vinxi is the full-stack JavaScript SDK by TanStack — a composable toolkit for building frameworks, powers TanStack Start and SolidStart, combines Vite + Nitro with routers and server functions. vike (formerly vite-plugin-ssr) is the flexible Vite-based framework — bring your own UI framework (React, Vue, Solid), file-based routing, SSR/SSG/SPA modes, highly customizable. waku is the minimal React Server Components framework — built for RSC from the ground up, lightweight alternative to Next.js, by Daishi Kato (Zustand author). In 2026: vinxi for building custom frameworks, vike for flexible multi-framework SSR, waku for minimal React Server Components.
Key Takeaways
- vinxi: ~100K weekly downloads — TanStack, framework SDK, Vite + Nitro, server functions
- vike: ~50K weekly downloads — Vite plugin, any UI framework, SSR/SSG/SPA
- waku: ~10K weekly downloads — minimal RSC framework, React-only, by Daishi Kato
- vinxi is a framework-building toolkit (meta-framework); vike and waku are frameworks
- vike supports React, Vue, Solid, Svelte — most flexible
- waku is the simplest path to React Server Components without Next.js
vinxi
vinxi — full-stack SDK:
How it works
// vinxi is a toolkit for building frameworks
// It combines: Vite (bundler) + Nitro (server) + routers
// app.config.js — minimal vinxi app:
import { createApp } from "vinxi"
export default createApp({
routers: [
{
name: "public",
type: "static",
dir: "./public",
},
{
name: "client",
type: "client",
handler: "./app/client.tsx",
target: "browser",
build: {
rollupOptions: {
input: "./app/client.tsx",
},
},
},
{
name: "ssr",
type: "http",
handler: "./app/server.tsx",
target: "server",
},
],
})
Server functions
// vinxi enables "use server" directive:
// app/actions.ts
"use server"
import { db } from "./db"
export async function createPost(title: string, content: string) {
const post = await db.post.create({
data: { title, content },
})
return post
}
export async function getPosts() {
return db.post.findMany({
orderBy: { createdAt: "desc" },
})
}
// Client component can call these directly:
// import { createPost } from "./actions"
// await createPost("Hello", "World")
// → RPC call to server, no manual API routes
TanStack Start (built on vinxi)
// TanStack Start uses vinxi under the hood:
// app/routes/index.tsx
import { createFileRoute } from "@tanstack/react-router"
import { createServerFn } from "@tanstack/start"
const getPosts = createServerFn("GET", async () => {
const posts = await db.post.findMany()
return posts
})
export const Route = createFileRoute("/")({
loader: () => getPosts(),
component: HomePage,
})
function HomePage() {
const posts = Route.useLoaderData()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
SolidStart (built on vinxi)
// SolidStart also uses vinxi:
// src/routes/index.tsx
import { createAsync, query } from "@solidjs/router"
const getPosts = query(async () => {
"use server"
return db.post.findMany()
}, "posts")
export default function Home() {
const posts = createAsync(() => getPosts())
return (
<ul>
<For each={posts()}>{(post) =>
<li>{post.title}</li>
}</For>
</ul>
)
}
vike
vike (formerly vite-plugin-ssr) — flexible Vite framework:
Setup with React
// vite.config.ts
import react from "@vitejs/plugin-react"
import vike from "vike/plugin"
export default {
plugins: [react(), vike()],
}
// pages/index/+Page.tsx
export default function Page() {
return (
<div>
<h1>Welcome to PkgPulse</h1>
<p>Compare npm packages</p>
</div>
)
}
// pages/index/+data.ts
export async function data() {
const packages = await fetch("https://api.pkgpulse.com/trending")
.then((res) => res.json())
return { packages }
}
// pages/index/+Page.tsx (with data)
import { useData } from "vike-react/useData"
export default function Page() {
const { packages } = useData()
return <PackageList packages={packages} />
}
Works with any UI framework
// With Vue:
// vite.config.ts
import vue from "@vitejs/plugin-vue"
import vike from "vike/plugin"
import vikeVue from "vike-vue/plugin"
export default {
plugins: [vue(), vike(), vikeVue()],
}
// pages/index/+Page.vue
<script setup>
import { useData } from "vike-vue/useData"
const { packages } = useData()
</script>
<template>
<h1>PkgPulse</h1>
<ul>
<li v-for="pkg in packages" :key="pkg.name">
{{ pkg.name }}
</li>
</ul>
</template>
// With Solid:
// vite.config.ts — same pattern with vike-solid
SSR / SSG / SPA modes
// pages/blog/+config.ts
export default {
// Pre-render at build time (SSG):
prerender: true,
}
// pages/dashboard/+config.ts
export default {
// Client-side only (SPA):
ssr: false,
}
// pages/index/+config.ts
export default {
// Server-side rendered (SSR) — default:
ssr: true,
}
// Global config — vike.config.ts:
export default {
prerender: true, // SSG for entire site
// Or: ssr: false // SPA for entire site
}
Custom hooks and layouts
// pages/+onBeforeRender.ts — runs before every page render:
export async function onBeforeRender(pageContext) {
const user = await getUser(pageContext.headers.cookie)
return {
pageContext: {
user,
},
}
}
// pages/+Layout.tsx — shared layout:
import { usePageContext } from "vike-react/usePageContext"
export default function Layout({ children }) {
const { user } = usePageContext()
return (
<div>
<nav>{user ? `Hi ${user.name}` : "Login"}</nav>
<main>{children}</main>
</div>
)
}
// pages/admin/+guard.ts — route guard:
export async function guard(pageContext) {
if (!pageContext.user?.isAdmin) {
throw redirect("/login")
}
}
waku
waku — minimal React Server Components:
Basic setup
// waku.config.ts
export default {
// Minimal config — convention over configuration
}
// src/pages/index.tsx — Server Component by default
import { getPackages } from "../lib/db"
export default async function HomePage() {
const packages = await getPackages()
return (
<div>
<h1>PkgPulse</h1>
<PackageList packages={packages} />
</div>
)
}
// Server Components can directly access databases, APIs, etc.
// No "use server" needed — components are server-side by default
Client components
// src/components/SearchBox.tsx
"use client"
import { useState } from "react"
export function SearchBox() {
const [query, setQuery] = useState("")
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search packages..."
/>
)
}
// Use in a server component:
// src/pages/index.tsx
import { SearchBox } from "../components/SearchBox"
export default async function HomePage() {
const packages = await db.query("SELECT * FROM packages")
return (
<div>
<SearchBox />
<PackageList packages={packages} />
</div>
)
}
Server actions
// src/actions.ts
"use server"
export async function createPackage(formData: FormData) {
const name = formData.get("name") as string
await db.insert(packages).values({ name })
}
// src/components/AddPackageForm.tsx
"use client"
import { createPackage } from "../actions"
export function AddPackageForm() {
return (
<form action={createPackage}>
<input name="name" placeholder="Package name" />
<button type="submit">Add</button>
</form>
)
}
File-based routing
src/
├── pages/
│ ├── index.tsx → /
│ ├── about.tsx → /about
│ ├── blog/
│ │ ├── index.tsx → /blog
│ │ └── [slug].tsx → /blog/:slug
│ └── _layout.tsx → shared layout
├── components/
│ └── SearchBox.tsx
└── lib/
└── db.ts
// src/pages/_layout.tsx
import type { ReactNode } from "react"
export default function Layout({ children }: { children: ReactNode }) {
return (
<html>
<body>
<nav>PkgPulse</nav>
<main>{children}</main>
</body>
</html>
)
}
// src/pages/blog/[slug].tsx
interface Props {
slug: string
}
export default async function BlogPost({ slug }: Props) {
const post = await db.query("SELECT * FROM posts WHERE slug = ?", [slug])
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
)
}
Feature Comparison
| Feature | vinxi | vike | waku |
|---|---|---|---|
| Purpose | Framework SDK | Flexible framework | Minimal RSC framework |
| UI frameworks | Any (React, Solid) | Any (React, Vue, Solid) | React only |
| React Server Components | ✅ (via frameworks) | ✅ (vike-react) | ✅ (core feature) |
| Server functions | ✅ ("use server") | ✅ | ✅ |
| File-based routing | Via frameworks | ✅ | ✅ |
| SSR | ✅ | ✅ | ✅ |
| SSG | ✅ | ✅ | ✅ |
| SPA mode | ✅ | ✅ | ❌ |
| Deploy targets | Any (via Nitro) | Any (via Vite) | Node.js, Cloudflare |
| Used by | TanStack Start, SolidStart | Custom apps | Standalone |
| Weekly downloads | ~100K | ~50K | ~10K |
When to Use Each
Use vinxi if:
- Building a custom full-stack framework
- Using TanStack Start or SolidStart
- Need Vite + Nitro with custom router configuration
- Want server functions with "use server" support
Use vike if:
- Want to use any UI framework (React, Vue, Solid, Svelte)
- Need flexible SSR/SSG/SPA per-page configuration
- Want maximum customization (hooks, guards, layouts)
- Prefer convention-based routing with escape hatches
Use waku if:
- Want the simplest React Server Components setup
- Building a lightweight alternative to Next.js
- Want RSC without framework overhead
- Prefer minimal configuration and conventions
React Server Components Architecture Deep Dive
Both waku and vike-react implement React Server Components, but their approaches to the RSC runtime differ meaningfully. Waku implements its own RSC server runtime that handles component serialization, client component hydration, and the server/client boundary protocol — this gives waku full control over how RSC works but means you're relying on a smaller team's RSC implementation. vike-react delegates RSC to React's official implementation while adding its own routing and data-loading conventions on top. The practical implication: waku users get the simplest RSC experience (components are server-side by default, "use client" for client components) while vike users get more flexibility but need to configure the RSC layer explicitly. Both require careful attention to the server/client module graph — accidentally importing a server-only module (database driver, file system access) from a client component causes build failures that can be cryptic until you understand the RSC module boundary rules.
TypeScript End-to-End Type Safety
The full-stack TypeScript story differs substantially across these three frameworks. vinxi's server functions with "use server" provide RPC-style type safety: if you define export async function getPackages(): Promise<Package[]> in a server file, the client-side call site knows the return type without any manual typing. vike's +data.ts and useData() hooks provide type safety through a TypeScript convention: define and export PageData from your data file, then useData<PageData>() in the page component. waku's server component model is the most TypeScript-natural since server components are just async functions — there's no abstraction layer between your TypeScript types and the component's props. For API route type safety in all three, tRPC or similar can be added as a layer, though it's somewhat redundant when server functions already provide typed RPC.
Deployment Targets and Edge Compatibility
Deployment compatibility is a key differentiator in 2026. vinxi uses Nitro as its server layer, which supports deployment to Node.js, Cloudflare Workers, Vercel, Netlify, AWS Lambda, Deno Deploy, and more — Nitro's preset system makes multi-target deployment a build-time configuration choice. vike builds on Vite and supports the same diverse deployment targets as Vite plugins, though the specific deployment configuration depends on which adapter you use. waku explicitly targets Node.js and Cloudflare Workers, with Cloudflare Workers support being particularly notable since it enables RSC at the edge without paying for Vercel's high-performance tier. For teams building globally distributed applications where per-region server-side rendering latency matters, waku's Cloudflare Workers deployment path is a meaningful architectural advantage.
File-Based Routing Conventions Comparison
All three frameworks use file-based routing but with different conventions. vike's +Page.tsx, +data.ts, +config.ts convention places multiple page-specific files in a directory, separating concerns while keeping them colocated — this is more verbose than Next.js's single-file approach but more flexible since you can define config per-page without a special export syntax. waku follows a simpler src/pages/ directory structure where index.tsx maps to / and [slug].tsx maps to dynamic segments — familiar to Next.js users. vinxi, as a framework SDK, delegates routing conventions to the framework built on top of it: TanStack Start uses TanStack Router's file-based conventions, SolidStart uses SolidJS Router conventions. This means vinxi's routing story depends on which framework you're using, not vinxi itself.
Community Ecosystem and Production Readiness
These three frameworks have significantly different maturity profiles. vinxi is production-ready primarily through TanStack Start and SolidStart — two actively developed frameworks with committed corporate backing (Tanstack LLC and Netlify/SolidJS team). The underlying vinxi primitives are stable, but using vinxi directly (rather than through TanStack Start) means building on a framework-building toolkit without the framework's batteries. vike is the most production-mature of the three as a direct user-facing framework — it has been deployed in production applications since its vite-plugin-ssr days, has extensive documentation, and a stable API with clear migration paths between versions. waku is the newest and most experimental: its author Daishi Kato is well-respected in the React community (Zustand, Jotai, React-tracked) but waku's small download count (10K/week) reflects that most production teams still choose Next.js when they want RSC. The ecosystem around waku — plugins, adapters, community support — is thinner than vike's.
Migration Paths from Next.js
Teams evaluating alternatives to Next.js in 2026 typically have one of two motivations: wanting more control over the framework layer, or needing multi-framework support (React plus Vue or Solid in the same codebase). For control-motivated migrations, vike is the most natural Next.js alternative — it replicates Next.js's file-based routing and SSR model while exposing more internals. The migration path involves moving Next.js page files to vike's +Page.tsx convention, converting getServerSideProps to vike's +data.ts export, and replacing Next.js's <Link> with vike's equivalent. API routes migrate to whichever server framework you pair with vike (Hono, Fastify, Nitro). waku's simpler RSC model is appealing for teams who want to experiment with RSC without Next.js's full complexity, though the limited ecosystem means you'll need to integrate more primitives manually. The honest assessment: most teams considering these alternatives would benefit more from mastering Next.js's configuration options than from migrating to a smaller framework ecosystem.
Performance and Bundle Size Considerations
All three frameworks build on Vite or Nitro, which means their baseline build performance is excellent — significantly faster than webpack-based builds. vinxi's dual-router architecture (separate client and server routers) enables fine-grained code splitting that avoids sending server-only code to the browser. vike's per-page data files (+data.ts) are always server-only, which prevents accidental bundle inflation from including database drivers or server secrets in client bundles. waku's RSC model provides the strongest server/client bundle boundary: components without "use client" never appear in the browser bundle at all, which is the primary performance benefit of RSC over traditional SSR. For teams focused on Core Web Vitals and bundle size optimization, waku's RSC-first design produces the smallest initial JavaScript payloads by structural design rather than by requiring careful optimization effort. Measure your actual bundle output with vite-bundle-visualizer or the equivalent for your chosen framework before making performance-motivated architectural decisions.
Compare framework SDKs and full-stack tooling on PkgPulse →
See also: AVA vs Jest and The Rise of Full-Stack TypeScript: 2020 to 2026, Full-Stack JavaScript Toolkit 2026.