Connect your React app to a backend on the same origin
By Flavio Copes
How to serve a React and a server-side backend app from the same origin with Vite and Express, without CORS in development and with static files in production
The most common way to start a React app today is Vite. This post originally used create-react-app, which has been deprecated since early 2025. The examples below use Vite.
One problem you might stumble upon is how to connect it to a backend you already have, or one you might want to create.
In development
When developing the app you want to take advantage of hot reloading and all the other convenient features of Vite. How can you combine that with a backend without having to use CORS on the server and worry about ports?
I am going to provide an example using Express in the post, but this applies to any other framework.
Assuming you are testing this, let’s create a React app with the Vite React template (it scaffolds Vite 8 and React 19 as of September 2026):
npm create vite@latest testing -- --template react
cd testing
npm install
Now create a simple Express server in a server.js file, which you can add anywhere you want. It can even be in a separate folder altogether.
If you choose to add this file inside the Vite application code, run:
npm install express
And we’re ready to go.
Add this simple Express setup (the Vite template sets "type": "module" in package.json, so import works in server.js):
import express from 'express'
const app = express()
app.get('/hey', (req, res) => res.send('ho!'))
app.listen(8080)
Crucial point here: open vite.config.js and add a server.proxy entry so the Vite dev server forwards API calls to Express:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/hey': 'http://localhost:8080',
},
},
})
This tells Vite to proxy those requests to the Node.js server built with Express. Same-origin from the browser’s point of view. No CORS.
Now run this Node process using node server.js. In another window you start the Vite app using npm run dev.
When the browser opens on port 5173 (by default), open the DevTools and run:
fetch('/hey')
If you check the network panel, you should have a successful response with the ho! message.
In production
In production, you are going to run npm run build when you are ready to deploy and we will use the Express server to serve those static files from Vite’s dist folder.
The proxy config is a development-only helper. It does nothing in production, and you can leave it in vite.config.js.
In the code below, we tell the app to serve the static build of the React app:
import express from 'express'
import path from 'path'
import { fileURLToPath } from 'url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const app = express()
app.use(express.static(path.join(__dirname, 'dist')))
app.get('/ping', (req, res) => {
return res.send('pong')
})
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'dist', 'index.html'))
})
app.listen(8080)
Build with npm run build, then start Express. Browser, React assets, and API all share the same origin.
Want me to talk about your product? You can sponsor this site.