I've deployed my MERN app on Heroku and everything was fine until I realized an issue every time I refresh the page or try to access a route from the address bar. While navigation through React Router links is fine, trying to go directly to a route from URL address bar or refreshing the page is causing the app to break and sending server responses directly to the browser instead of rendering the component - to clarify: if a given route was supposed to make a GET request and display some data, the actual JSON is displayed on-screen.
As far as I've checked, this is happening only on components that make a GET request.
server.js
// INITIATE APP
const app = express();
// MIDDLEWARE
app.use(express.json());
app.use(cookieParser());
app.use(cors());
app.use(passport.initialize());
const isProduction = process.env.NODE_ENV === "production";
// Priority serve any static files
isProduction &&
app.use(express.static(path.resolve(__dirname, "../client/build")));
// CONNECT TO DATABASE
// db config
const dbConnection = isProduction
? process.env.MONGO_URI_PROD
: process.env.MONGO_URI_DEV;
mongoose.connect(dbConnection, () => {
console.log("Successfully connected to database");
});
// ROUTES
const authRouter = require("./routes/auth");
const detailsRouter = require("./routes/details");
const diaryRouter = require("./routes/diary");
const resultsRouter = require("./routes/results");
const settingsRouter = require("./routes/settings");
app.use("/auth", authRouter);
app.use("/details", detailsRouter);
app.use("/diary", diaryRouter);
app.use("/results", resultsRouter);
app.use("/settings", settingsRouter);
// All remaining requests return the React app, so it can handle routing
isProduction &&
app.get("*", function (request, response) {
response.sendFile(path.resolve(__dirname, "../client/build", "index.html"));
});
// DEFINE PORTS
const port = process.env.PORT || 5000;
// START SERVER
app.listen(port, () => {
console.log(`Server started on port ${port}`);
});
By the end of the code above, right before Define ports, there is this statement that I included to fix a similar issue happening on other routes too. I thought this was going to prevent this error in any situation, but it seems that this isn't the case. You can try the app here (you can delete the account on /settings after testing).
I'd like to thank S. Elliott Johnson for the solution I'll post below to anyone running into the same issue in the future:
This sounds like intended behavior. Your server routes and your React Router routes SHOULD NOT conflict.
React Router isn't actually "routing" anywhere from a HTTP sense -- it's just rendering different JavaScript/HTML and storing its "location" in the URL.
When running a React app, the React app is typically only served from the root of your website (or some other "root", like mydomain.com/app). When you make a
HTTP GETrequest to that route, the backend server sends all of the JavaScript, HTML, and CSS necessary to bootstrap your React app. Clicking around using React Router simply causes your React code to run on the client.When you actually reload the page, your browser, as you know, makes a
GETrequest back to the server for that route, so you just get whatever your server sends. Let's use a few examples where you have a React app that's served from my domain.com.Example 1:
User makes a browser GET request to mydomain.com. They receive the React app back
User navigates to
/auth/login- noHTTPrequests, React simply running codeUser navigates to
/meto view their account -- again, sameUser reloads the page using the browser - a
HTTP GETrequest is sent to the backend, and they'll receive whatever the backend sends back -- whether that'sJSONor something elseYou really have two options here:
Redirect all
HTTPrequests to root, meaning/,/somethingand/anythingwill serve/. Then host your API on another subdomain, like api.mydomain.comChoose a route to serve your API from, like
mydomain.com/api. Forward all requests from any route EXCEPT/apiand it's subroutes to the root.
What I ended up doing was option 2:
Renamed my API routes prepending /api to all of them on server.js. Then I renamed all API calls on React accordingly. That code excerpt
isProduction &&
app.get("*", function (request, response) {
response.sendFile(path.resolve(__dirname, "../client/build", "index.html"));
});
on server.js took care of the GET requests executed on page refresh, making sure that Node always serves index.html to supply those requests.