Developing a full-stack Single Page Application (SPA) often means juggling two separate servers — one for the frontend and one for the backend — running on different ports. This leads to extra configuration, CORS headaches, and a fragmented development experience. Enter vite-spa-server: a powerful Vite plugin that unifies your frontend and backend on a single port.
One Port, One Fight!
Designed for the Single Fighter Developer,
vite-spa-serverlets you build modern full-stack apps with ease, using React + Express (or any other combo) — all served from the same port during development and production.
Why Use vite-spa-server?
| Problem | Solution with vite-spa-server
|
|---|---|
Two ports (3000 for Vite, 5000 for API) |
Single port (e.g., 3000) for both |
| CORS configuration | Not needed — same origin |
Proxy setup in vite.config.ts
|
Eliminated |
| HMR only for frontend | Full HMR support while backend runs |
| Complex production deployment | Simple node dist
|
Features
- Single-Port Architecture – Frontend and backend share the same port.
- Type Sharing – Easily share TypeScript types between client and server.
- Framework Agnostic Backend – Works with Express, Hono, NHttp, etc.
- HMR for Frontend – Instant reloads during development.
- Zero-config Integration – Works with React, Vue, Svelte, etc.
-
Multiple Apps via
area– Serve different SPAs from subpaths (/admin,/app, etc.) -
Simple Production Build – Just run
node dist
Getting Started: React + Express in One Port
Let’s build a minimal full-stack app with React as the frontend and Express as the backend — all on port 3000.
Step 1: Create a Vite + React Project
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
Step 2: Install Dependencies
npm install express
npm install vite-spa-server @types/express --save-dev
Step 3: Configure vite.config.ts
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import spaServer from "vite-spa-server";
export default defineConfig({
plugins: [
react(),
spaServer({
entry: "./src/server.ts",
port: 3000,
serverType: "express",
}),
],
});
Step 4: Create Backend Server
// ./src/server.ts
import express from "express";
const app = express();
// API route — accessible at http://localhost:3000/api/user
app.get("/api/user", (req, res) => {
res.json({
name: "John",
message: "Hello from the backend!",
});
});
export default app;
Step 5: Use the API in React
// src/App.tsx
import { useEffect, useState } from "react";
function App() {
const [data, setData] = useState<any>(null);
useEffect(() => {
fetch("/api/user")
.then((res) => res.json())
.then(setData);
}, []);
return (
<div>
<h1>Vite + React + Express (Single Port!)</h1>
{data ? (
<p>
{data.message} – <strong>{data.name}</strong>
</p>
) : (
<p>Loading...</p>
)}
</div>
);
}
export default App;
Step 6: Run Development Server
npm run dev
Open http://localhost:3000 — your React app loads, and the /api/user endpoint is served from the same port!
Production Build
npm run build
node dist
The built server runs your Express backend and serves the static React files — no extra reverse proxy needed.
Advanced: Handle 404 for API Routes
Avoid fallback to index.html for unknown API routes:
app.use((req, res, next) => {
if (req.url.startsWith("/api")) {
res.header("spa-server", "false").status(404);
return res.send({ error: "API endpoint not found" });
}
next();
});
Multiple Apps with area
Want to serve /admin, /dashboard, etc., as separate SPAs?
spaServer({
port: 3000,
serverType: "express",
area: {
"/": "./index.html",
"/admin": "./admin.html",
"/sign": "./sign.html",
},
})
Each path serves its own HTML entry point — perfect for micro-frontends or multi-app dashboards.
Configuration Options
| Option | Description | Default |
|---|---|---|
port |
Server port | — |
serverType |
Backend framework (express, hono, etc.) |
— |
entry |
Backend entry file | ./src/server.ts |
runtime |
Runtime (node) |
node |
area |
Map paths to HTML files | — |
routerType |
Frontend router (browser, hash, none) |
— |
License
Conclusion
With vite-spa-server, you no longer need to:
- Run two dev servers
- Configure CORS
- Set up proxies
- Deploy separate services
Just one port, one process, one command.
Perfect for solo developers, prototypes, or full production apps.
GitHub: https://github.com/herudi/vite-spa-server
NPM: npm i vite-spa-server -D
One Port. One Fight. Win.
Top comments (2)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.