DEV Community

herudi
herudi

Posted on • Edited on

React + Express + 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-ts
cd my-app
npm install
Enter fullscreen mode Exit fullscreen mode

Step 2: Install Dependencies

npm install express
npm install vite-spa-server @types/express --save-dev
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({
      entry: "./src/server.ts",
      port: 3000,
      serverType: "express",
    }),
  ],
});
Enter fullscreen mode Exit fullscreen mode

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;
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.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 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.