Skip to main content

Guide

vinxi vs vike vs waku (2026)

Compare vinxi, vike, and waku for building full-stack JavaScript frameworks. Server functions, SSR, file-based routing, and which framework SDK to use in 2026.

·PkgPulse Team·
0

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

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.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.