Skip to main content

vinxi vs vike vs waku: Full-Stack Framework SDKs in JavaScript (2026)

·PkgPulse Team

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

Featurevinxivikewaku
PurposeFramework SDKFlexible frameworkMinimal RSC framework
UI frameworksAny (React, Solid)Any (React, Vue, Solid)React only
React Server Components✅ (via frameworks)✅ (vike-react)✅ (core feature)
Server functions✅ ("use server")
File-based routingVia frameworks
SSR
SSG
SPA mode
Deploy targetsAny (via Nitro)Any (via Vite)Node.js, Cloudflare
Used byTanStack Start, SolidStartCustom appsStandalone
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.

Compare framework SDKs and full-stack tooling on PkgPulse →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.