Skip to content

Fix: Vinxi Not Working — Dev Server Not Starting, Routes Not Matching, or Build Failing

FixDevs ·

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 Found

Or 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 orchestrates multiple “routers” (server, client, SSR) in a single application:

  • Vinxi needs an app.config.js — the configuration file defines routers, their modes (server, client, SSR), and how they compose together. Without it, Vinxi has nothing to build.
  • Routers have different capabilities — a "server" router handles API routes and server functions. A "client" router bundles client-side JavaScript. An "ssr" router renders on the server and hydrates on the client. Mixing code between router types causes runtime errors.
  • File-system routing depends on the router configuration — each router can have its own dir for file-based routing. Files outside the configured directory aren’t recognized as routes.
  • Vinxi uses Nitro under the hood — the server runtime comes from Nitro (same as Nuxt). Server configuration, presets, and deployment targets follow Nitro conventions.

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.

For related framework issues, see Fix: TanStack Start Not Working and Fix: Nitro Not Working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles