vinxi vs vike vs waku: Full-Stack Framework SDKs in JavaScript (2026)
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
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on vinxi v0.4.x, vike v0.4.x, and waku v0.21.x.