Fix: Vinxi Not Working — Dev Server Not Starting, Routes Not Matching, or Build Failing
Part of: JavaScript & TypeScript Errors
Quick Answer
How to fix Vinxi server framework issues — app configuration, routers, server functions, middleware, static assets, and deployment to different platforms.
The Problem
The Vinxi dev server fails to start:
npx vinxi dev
# Error: No app config found — or —
# Error: Cannot find module 'vinxi'Or routes return 404:
GET /api/users → 404 Not FoundOr server and client code mix incorrectly:
ReferenceError: process is not defined (in client bundle)Why This Happens
Vinxi is a full-stack JavaScript SDK that powers frameworks like TanStack Start and SolidStart. It is closer to a meta-framework toolkit than a framework: it orchestrates multiple “routers” (server, client, SSR, static) in a single application and hands the result to Nitro for serving and deployment. Most “Vinxi not working” reports are really about understanding what Vinxi is doing under the hood when a higher-level framework forwards an error message up.
Vinxi needs an app.config.js (or .ts) at the project root. The configuration file defines routers, their modes, base paths, handlers, and Vite plugins. Without it, the CLI throws “No app config found” and exits before doing anything else. Frameworks built on Vinxi ship their own config — SolidStart generates app.config.ts with defineConfig from @solidjs/start/config, TanStack Start generates one with defineConfig from @tanstack/start/config. The framework wrappers feed into the same Vinxi createApp call, so when something breaks it usually breaks at the Vinxi layer and the error you see is forwarded from there.
Routers have different capabilities and they do not share code automatically. A 'http' router handles API and SSR endpoints on the server. A 'client' router bundles client-side JavaScript for the browser. A 'spa' router serves a single-page app. A 'static' router serves files from a directory. Importing a module that uses fs, crypto, or a database driver from inside a client router throws “process is not defined” or “Module not found” at build time — the Vite bundle for the browser cannot resolve those Node built-ins. This is the source of most “server code leaked into client” errors when you start adding helpers that import from node:fs.
File-system routing depends on the router. Each router can have its own dir for file-based routing and its own naming style (Next.js bracket style, file-path-as-route, etc). Files outside the configured directory aren’t recognized as routes. If you move a route handler and forget to update dir in app.config.js, you get a 404 with no other signal.
Vinxi delegates the production runtime to Nitro — the same server engine Nuxt uses. That means deployment presets (node-server, vercel, netlify, cloudflare-pages, bun) and the .output directory layout follow Nitro conventions. If you have used Nuxt before, the Nitro mental model carries over.
Version History: Vinxi and the Meta-Framework Reshuffle
Vinxi was extracted from SolidStart in 2023 by Nikhil Saraf. The original SolidStart was a tightly-coupled SSR framework for Solid; Vinxi 0.1 split the server/client orchestration layer out so other frameworks could reuse it. The decision turned out to matter beyond Solid — TanStack Start adopted Vinxi as its underlying engine in early 2024, and several smaller experimental frameworks did the same.
Vinxi 0.1 (mid-2023) shipped the core idea: multiple routers in one config, Vite-based dev server, simple HTTP handler interface. Vinxi 0.2 added file-system routing helpers and improved the SSR router so frameworks could implement streaming server rendering with less custom code. Vinxi 0.3, released in early 2024, switched the production runtime to Nitro, which brought first-class deployment presets for Vercel, Netlify, Cloudflare Pages, and Bun. That release is the dividing line in most tutorials — anything written before “Vinxi + Nitro” is talking about an older shape of the framework that does not match the current app.config.ts semantics.
The relationship with SolidStart is the part that confuses newcomers. SolidStart 0.x (before 1.0) was its own framework with its own SSR machinery. After the Vinxi extraction, SolidStart 1.0 was rebuilt on top of Vinxi and is effectively a Solid-flavored preset for Vinxi’s routers and a Solid-specific server-function compiler. If you read a SolidStart guide that talks about solid-start.config.ts or imports from solid-start, you are looking at the pre-1.0 framework. The current guides import from @solidjs/start and ship app.config.ts.
TanStack Start uses Vinxi for the same reason: it gives the framework a Vite-native dev server, multi-router builds, Nitro-powered deployment, and server functions for free. The framework on top contributes the routing primitives (createFileRoute, createRoute), the data loaders, and the type-safe link components. When you hit a build error in TanStack Start that mentions routers or defineConfig or vinxi/http, you are debugging Vinxi.
Compared to Next.js with the App Router, Vinxi is the layer Next.js does not expose. Next.js bundles all of Turbopack, the SSR engine, the routing layer, and the deployment glue into a single CLI with no way to swap any of it out. Vinxi splits those pieces out and lets each framework opinionate over the pieces it cares about. That is why Vinxi-based frameworks tend to ship later than Next.js but with more flexibility — TanStack Start can support Solid and React from the same engine because the router lives above Vinxi and the rendering lives inside a Vinxi router handler. The cost is that you, the application author, occasionally have to understand both layers: a build error from the framework can be a Vinxi-level configuration mistake, and the framework cannot always translate it cleanly.
The deployment surface is where new users get tripped up most often. vinxi build produces a .output/ directory whose shape depends on the preset. node-server (the default) writes a self-contained Node server you can run with node .output/server/index.mjs. vercel writes the Vercel Functions output format, with index.func/ directories that match Vercel’s runtime expectations. cloudflare-pages writes a Worker bundle without Node built-ins, which means fs, child_process, and any package that imports them will fail at runtime. bun writes a Bun-runnable server. Switching presets without testing locally is the most common production breakage. Always run vinxi build --preset=<target> locally before deploying to a new target and test the produced .output against the runtime the preset assumes.
Fix 1: Basic Vinxi App
npm install vinxi// app.config.js
import { createApp } from 'vinxi';
export default createApp({
routers: [
// Static file server (public/)
{
name: 'public',
type: 'static',
dir: './public',
},
// API server routes
{
name: 'api',
type: 'http',
handler: './app/api.ts',
base: '/api',
},
// Client-side SPA
{
name: 'client',
type: 'spa',
handler: './app/client.tsx',
target: 'browser',
base: '/',
plugins: () => [/* vite plugins */],
},
],
});// app/api.ts — server handler
import { eventHandler, getQuery, readBody, createRouter, defineEventHandler } from 'vinxi/http';
const router = createRouter();
router.get('/users', defineEventHandler(async (event) => {
const query = getQuery(event);
const users = await db.query.users.findMany({
limit: Number(query.limit) || 20,
});
return users;
}));
router.post('/users', defineEventHandler(async (event) => {
const body = await readBody(event);
const [user] = await db.insert(users).values(body).returning();
return user;
}));
export default router.handler;// app/client.tsx — client entry
import { createRoot } from 'react-dom/client';
function App() {
return <div>Hello from Vinxi!</div>;
}
createRoot(document.getElementById('root')!).render(<App />);Fix 2: SSR Application
// app.config.js — full SSR setup
import { createApp } from 'vinxi';
import react from '@vitejs/plugin-react';
export default createApp({
routers: [
{
name: 'public',
type: 'static',
dir: './public',
},
{
name: 'api',
type: 'http',
base: '/api',
handler: './app/api.ts',
},
{
name: 'ssr',
type: 'http',
handler: './app/ssr.tsx',
target: 'server',
plugins: () => [react()],
},
{
name: 'client',
type: 'client',
handler: './app/client.tsx',
target: 'browser',
base: '/_build',
plugins: () => [react()],
},
],
});// app/ssr.tsx — server-side rendering handler
import { eventHandler } from 'vinxi/http';
import { renderToString } from 'react-dom/server';
import { getManifest } from 'vinxi/manifest';
export default eventHandler(async (event) => {
const clientManifest = getManifest('client');
const assets = await clientManifest.inputs[clientManifest.handler].assets();
const html = renderToString(<App />);
return `<!DOCTYPE html>
<html>
<head>
${assets.map(asset =>
asset.tag === 'script'
? `<script src="${asset.attrs.src}" ${asset.attrs.type ? `type="${asset.attrs.type}"` : ''}></script>`
: asset.tag === 'link'
? `<link rel="${asset.attrs.rel}" href="${asset.attrs.href}" />`
: ''
).join('\n')}
</head>
<body>
<div id="root">${html}</div>
</body>
</html>`;
});Fix 3: File-System Routing
// app.config.js — file-based API routes
import { createApp } from 'vinxi';
export default createApp({
routers: [
{
name: 'api',
type: 'http',
base: '/api',
dir: './app/api', // Directory-based routing
handler: './app/api/_handler.ts',
routes: (router, app) =>
new FileSystemRouter({
dir: './app/api',
style: 'nextjs', // [param] style routes
}),
},
],
});app/api/
├── _handler.ts # Catch-all handler
├── users/
│ ├── index.get.ts # GET /api/users
│ ├── index.post.ts # POST /api/users
│ └── [id].get.ts # GET /api/users/:id
└── health.get.ts # GET /api/health// app/api/users/index.get.ts
import { eventHandler } from 'vinxi/http';
export default eventHandler(async () => {
return db.query.users.findMany();
});
// app/api/users/[id].get.ts
import { eventHandler, getRouterParam } from 'vinxi/http';
export default eventHandler(async (event) => {
const id = getRouterParam(event, 'id');
const user = await db.query.users.findFirst({ where: eq(users.id, id) });
if (!user) throw createError({ statusCode: 404, message: 'Not found' });
return user;
});Fix 4: Middleware
// app/middleware.ts
import { eventHandler, getCookie, setCookie } from 'vinxi/http';
// CORS middleware
export const corsMiddleware = eventHandler((event) => {
setResponseHeaders(event, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
});
if (event.method === 'OPTIONS') {
setResponseStatus(event, 204);
return '';
}
});
// Auth middleware
export const authMiddleware = eventHandler(async (event) => {
const token = getRequestHeader(event, 'authorization')?.replace('Bearer ', '');
if (!token) {
throw createError({ statusCode: 401, message: 'Unauthorized' });
}
const user = await verifyToken(token);
event.context.user = user;
});
// Apply in app config
export default createApp({
routers: [
{
name: 'api',
type: 'http',
base: '/api',
handler: './app/api.ts',
middleware: './app/middleware.ts', // Applied to all routes in this router
},
],
});Fix 5: Server Functions
// Vinxi supports server functions (used by TanStack Start, SolidStart)
// These compile to RPC endpoints automatically
// app/server/functions.ts
'use server';
export async function getUsers() {
// This code only runs on the server
return db.query.users.findMany();
}
export async function createUser(name: string, email: string) {
const [user] = await db.insert(users).values({ name, email }).returning();
return user;
}
// Called from client code — transparently makes HTTP request
import { getUsers, createUser } from './server/functions';
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
getUsers().then(setUsers);
}, []);
async function handleCreate() {
const user = await createUser('Alice', '[email protected]');
setUsers(prev => [...prev, user]);
}
return (/* ... */);
}Fix 6: Deployment
# Build for production
npx vinxi build
# The output depends on the server preset
# Default: .output/ directory with Nitro server
# Run production server
node .output/server/index.mjs// app.config.js — deployment presets
export default createApp({
server: {
preset: 'node-server', // Standard Node.js
// preset: 'vercel', // Vercel
// preset: 'netlify', // Netlify
// preset: 'cloudflare-pages', // Cloudflare
// preset: 'bun', // Bun runtime
},
routers: [/* ... */],
});Still Not Working?
“No app config found” — Vinxi looks for app.config.js (or .ts) in the project root. If using TanStack Start or SolidStart, the config file may be named differently (app.config.ts with framework-specific defineConfig).
Routes return 404 — check the base path. If a router has base: '/api', routes inside it are prefixed with /api. Also verify the handler file exists and exports a valid handler.
Client code imports server modules — Vinxi separates server and client bundles. Importing a module that uses fs, db, or other server APIs in client code causes “module not found” errors. Use server functions or the 'use server' directive.
Build fails with Vite errors — Vinxi uses Vite internally. Plugin compatibility issues (React, CSS) show as Vite errors. Check that plugins are returned from the plugins() function, not set directly as an array.
Old SolidStart guide doesn’t match the current API — pre-1.0 SolidStart was its own framework; the 1.0+ line is built on Vinxi. If a guide imports from solid-start or references solid-start.config.ts, it predates the Vinxi rewrite. Use the guides that import from @solidjs/start and ship app.config.ts. The same applies to anything written before Vinxi 0.3 (Nitro adapter) — the deployment story is different.
Deployment preset works locally but fails in production — Vinxi uses Nitro for the production server, and each preset has its own runtime quirks. cloudflare-pages runs Workers, so Node built-ins like fs and child_process are not available. vercel runs serverless functions with a cold-start budget. node-server is the closest to local dev. Match the preset to the environment you actually deploy to; do not use node-server in development and vercel in production without testing the vercel build locally with vinxi build --preset=vercel.
vinxi/http exports change between versions — the vinxi/http re-exports are tracked separately from h3 (the underlying server library). After bumping Vinxi, check the import path for eventHandler, getQuery, readBody, and friends. Some moved to h3 directly in newer Vinxi versions; framework wrappers usually re-export the right shape but a bare Vinxi project sees the change directly.
For related framework issues, see Fix: TanStack Start Not Working, Fix: SolidStart Not Working, Fix: Nuxt Not Working, and Fix: Waku Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Deno PermissionDenied — Missing --allow-read, --allow-net, and Other Flags
How to fix Deno PermissionDenied (NotCapable in Deno 2) errors — the right permission flags, path-scoped permissions, deno.json permission sets, and the Deno.permissions API.
Fix: Fastify Not Working — 404, Plugin Encapsulation, and Schema Validation Errors
How to fix Fastify issues — route 404 from plugin encapsulation, reply already sent, FST_ERR_VALIDATION, request.body undefined, @fastify/cors, hooks not running, and TypeScript type inference.
Fix: Analog Not Working — Routes Not Loading, API Endpoints Failing, or Vite Build Errors
How to fix Analog (Angular meta-framework) issues — file-based routing, API routes with Nitro, content collections, server-side rendering, markdown pages, and deployment.
Fix: Angular SSR Not Working — Hydration Failing, Window Not Defined, or Build Errors
How to fix Angular Server-Side Rendering issues — @angular/ssr setup, hydration, platform detection, transfer state, route-level rendering, and deployment configuration.