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
I think the single most used way to start a React app today is Vite. create-react-app is deprecated, so I would not start a new project with it.
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 current Vite React template (Vite 8, React 19 ecosystem):
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:
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.