DEV Community

herudi
herudi

Posted on

Single-Port SPA: React and Express using Vite. Same Port in Dev or Prod.

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-server lets 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
Enter fullscreen mode Exit fullscreen mode

Step 2: Install Dependencies

npm install vite-spa-server --save-dev
npm install express
Enter fullscreen mode Exit fullscreen mode

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",
    }),
  ],
});
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Step 6: Run Development Server

npm run dev
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
});
Enter fullscreen mode Exit fullscreen mode

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",
  },
})
Enter fullscreen mode Exit fullscreen mode

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

MIT 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)

Collapse
 
roshan_sharma_7deae5e0742 profile image
roshan sharma

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?

Collapse
 
herudi profile image
herudi

Thanks...
So far, the area feature is useful for more than one SPAs. like /app1 and /app2 but one server.