Configuring Express Server to handle client-side routing in a MERN stack app scaffolded with Vite using react-router library in declarative mode

I am trying to implement routing in a MERN stack app scaffolded with Vite using react-router library in declarative mode. Everything is working as expected in development on my local machine. When I deploy to Render, I need to have my express server handle all non-api routes by serving index.html. I am unable to figure out the correct syntax to target the PATH to reference the index.html file with a GET request. I have tried:

app.get(‘*’,(req,res)=>res.sendFile(‘../dist/index.html’))

app.get(‘*’,(req,res)=>res.sendFile(path.join(path.resolve(), ‘client/dist’, ‘index.html’)))

app.get(‘*’,(req,res)=>res.sendFile(path.join(path.resolve(),‘/dist’,‘index.html’)))

app.get(‘*’,(req,res)=>res.sendFile(path.join(path.resolve(),‘dist’,‘index.html’)))

app.get(‘*’,(req,res)=>res.sendFile(path.join(path.resolve(),‘index.html’)))

All of which result in the same error:

/opt/render/project/src/node_modules/path-to-regexp/dist/index.js:73
throw new TypeError(Missing parameter name at ${i}: ${DEBUG_URL});
^

TypeError: Missing parameter name at 1: https ://git.new/pathToRegexpError

Disclaimer: I’m kind of new to a full MERN stack, so take this advice with a grain of salt, it may be dated or not the “proper” way to do things.

  1. You host your react (client) app as a static site, and that handles client side routing.
  2. The api is hosted as a “web service” on render
  3. Vite is not actually used in a deployment on render, unless maybe you’re doing SSR, instead it builds a dist bundle and index page that is served as a static site on render
  4. You may be using Vite’s proxy to re-route all “/api” routes to your server app when developing locally. What’s actually happening is you’re running a very thin dev server for the client alone, which is making server side calls to your api, so no cors issue, from the client perspective it’s just making a call to the same IP address it uses to get index.html, but in prod you need to configure cors to either allow all (“*” - not recommended!) or whitelist your specific client’s origin.
  5. In your client app, configure your ajax API (fetch, axios, etc.) call to use a baseUrl that is dependent on environment specific config (vite uses dotenv to load .env. files). e.g. create a .env.development file that has VITE_API_URL=localhost, VITE_API_PORT=<your local api app's port>, and create a .env.production file that changes VITE_API_URL to <yourapp>.onrender.com, and omit VITE_API_PORT. Then when constructing base url for your api client in the client app, you use process.env.VITE_API_URL/PORT to construct it for the specific environment. This will cause the the dist bundle to be built with that value hardcoded, since vite doesn’t actually run during deployment.
  6. Similarly I would recommend setting up environment variables to specifically whitelist your static site’s origin, or localhost:port when running in dev. You can use .env.production.local to keep a local record of any prod secrets, and then use those values to create environment variables with the same name on the render.com dashboard, and your live app will ingest them without needing to commit sensitive data to a repo.