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
cd my-app
npm install
Step 2: Install Dependencies
npm install vite-spa-server --save-dev
npm install express
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({
port: 3000,
serverType: "express",
}),
],
});
Step 4: Create Backend Server
// src/server/index.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/index.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 install vite-spa-server
One Port. One Fight. Win.
Top comments (2)
This is super neat! You’ve simplified full-stack SPA development by getting React and Express to run on the same port, no CORS headaches, no extra proxy setup, just smooth HMR and easy production builds.
Did you try the multi-app 'area' feature yet, or mostly using it for single SPAs so far?
Thanks...
So far, the area feature is useful for more than one SPAs. like
/app1and/app2but one server.