'React Router not working correctly on production with express backend

I have Single page application built with React and Express as backend.

There are only two pages/main components: the application(<SinglePage/ >) and Not found page(<PageNotFound / >).

I want to display the application when "/" url is requested and to display the Not Found when any other url is requested(for example /home, /test, /insertInvalidUrlHere etc.).

This logic works perfectly on Localhost, but when I deploy the app to the domain, the single page view is displayed correctly(on url '/'), but when any other url is requested the attached page below is displayed, not the Not found page as expected. I also found out that the Not found page is displayed only if index.html is appended to the URL(domain.com/index.html).

Wrong page

I already tried these:

  • placing <Navigate to"/404"/> at the end and changing the route path to "/404".
  • changing the route path to "" or entirely removing it.
  • using HashRouter
  • deleting the app and deploying it again and again from the start.

None of these worked.

I also tried to build the app locally, and then use http-serve ( npm i -g http-serve ) to serve the build folder. Everything is displayed correctly in this case.

Could you please advise me, what I am doing wrong and what should be amended in order for the logic to work correctly when I deploy the app to the domain.

Frontend: App.tsx

import { BrowserRouter as Router , Route, Routes} from "react-router-dom";

import PageNotFound from "./Components/PageNotFound/PageNotFound";
import SinglePage from "./SinglePage";

function App() {
  return (
    <>
      <Router>
        <Routes>
          <Route path="/" element={<SinglePage />} />
          <Route path="*" element={<PageNotFound />} />
        </Routes>
      </Router>
    </>
  );
}

export default App; 

Backend: App.ts

const express = require("express");
const nodemailer = require("nodemailer");
const cors = require("cors");
const request = require("request");
var https = require('https');
var fs = require('fs');

const app = express();

app.use(express.json());
app.use(cors());

let transporter = nodemailer.createTransport({
  host: "mail.mail.com",
  port: 465,
  secure: true,
  auth: {
    user: "[email protected]",
    pass: "TestPassword123",
  }
});

transporter.verify((err, success) => {
  err
    ? console.log(err)
    : console.log(`Server is ready to receive messages: ${success}`);
});

app.post("/send", function (req, res) {

  let mailOptions = {
    from: `${req.body.mailerState.email}`,
    to: "[email protected]",
    subject: `Message from: ${req.body.mailerState.email}`,
    text: `${req.body.mailerState.message}`,
  };

  transporter.sendMail(mailOptions, function (err, data) {
    if (err) {
      res.json({
        status: "fail",
      });
    } else {
      console.log("Message Sent");
      res.json({
        status: "success",
      });
    }
  });
});

const port = 3001;

https
  .createServer(
    {
      key: fs.readFileSync("key.pem"),
      cert: fs.readFileSync("cert.pem"),
    },
    app
  )
  .listen(port, () => {
    console.log("Server is running at port 3001");
  });

app.get('/', (req, res) => {
  res.send("Hello from express server.")
}) 


Solution 1:[1]

I assume the error is locating in your router - since you have path="/" first, all subsequent urls ("/home","/test","/error/404", etc) will hit this path. try to use exact so your routing looks like this:

<Routes>
  <Route path="/" exact element={<SinglePage />} />
  <Route path="*" element={<PageNotFound />} />
</Routes>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Schiellerup