Serving static React app from server, crashing on refresh - node.js

I have made a React app which is hosted on the same machine as my API server as guided by this tutorial.
My directories are set up in such way that
- root
- server.js (entry for the API server)
- app (for all back-end stuff)
- client (directory for React app)
- public (static files used in the front-end)
- src (for all front-end stuff)
- build (directory containing static build)
- index.html (entry for front-end)
- ... (other bundled stuff)
And I am serving a static copy of my React app in below method.
// server.js
const app = express();
if (process.env.NODE_ENV === 'production') {
app.use(express.static('client/build'));
}
app.use('/api', routes); // for API routes
I am having a problem where, when I boot this up locally, (with NODE_ENV=production) everything smooth and page refreshes do not break the app.
However, after I have deployed this to Elastic Beanstalk, page refreshes break the app with this html error message being displayed in the browser.
Cannot GET /{whichever url path I had prior to refresh}
I could see in the logs that browser tried to send a request to the server with GET /{url_pathname}
I initially suspected something going funny with having both React router and Express router but then again, I am confused why this is not consistent with the case in the localhost.

If you are using NGINX, you need to update the configuration file located at
/etc/nginx/sites-available
location / {
try_files $uri $uri/ /index.html$is_args$args;
}
Or this if your app is not located in the default directory, for example "app-location" you would get this.
location /app-location {
root /var/www;
index index.html;
try_files $uri $uri/ /app-location/index.html;
}
And restart nginx
sudo service nginx restart

If you are running a react app that basically runs on a single index file, then your middleware router should have a catch-all route that always points to that index file somewhere.
app.get('/*',(req, res) => {
res.sendFile(path.join(__dirname + '/client/build/index.html'))
})
Please note that the current example is dependent on the path module...So:
import path from 'path'
// or
const path = require('path')

Related

express app is not sending index.html file to client

So my express app has a small Node server setup so it can serve up the index.html file when the home route '/' is hit. This is a requirement of using the App Services from Azure, there has to be this server.js file to tell the server how to serve up the client, and i had a previous implementation of this working, however i wanted to change my file structure. previously i had, the client React app in a folder client and the server.js in a folder server along with all of the conrtollers and routes. i've since moved the server API to its own application as there are other apps that depend on it. and i moved the client up one directory into the main directory. Everything was working fine till the other day when all of the sudden when you hit the home route / it will not serve up the index.html file. if you hit any other route it works, if you even hit a button linking back to the homepage, it works, but it wont serve up the app from the / and i cannot for the life of me figure out why, on my development server there are no errors in the console. and im most definitely targeting the correct directory and place for the index. but its like the server isnt reading the route to serve up.
if (process.env.NODE_ENV === 'production') {
console.log('running');
app.use(express.static(path.resolve(path.join(__dirname, 'build'))));
// no matter what route is hit, send the index.html file
app.get('*', (req, res) => {
res.sendFile(path.resolve(path.join(__dirname, 'build', 'index.html')));
});
} else {
app.get('/', (req, res) => {
res.send('API is running...');
});
}
So here im saying if the NODE_ENV is in production make the build folder static, and then whatever route is hit. (Note: i also tried this app.get with other route formats such as /* or / all have the same issues. however in my previous iteration when the client and server where deployed in the same location, /* is what i used.) The .env varialbes are setup correctly, as when the server is ran, itll console log running.. but even if i put a console log inside of the app.get() its like its never hit unless i access the route from something else first.
for example, if i place a console log inside of app.get that states hit whenever the route is hit, hitting / directly does nothing, but if i go to /login itll serve up the correct html on the client and console log hit in the terminal...
If you are having server files inside the client react app, then we are basically accessing file which are not inside our server file. So, we can serve static files using the following code:
const express = require("express");
const app = express(); // create express app
const path = require('path');
app.use(express.static(path.join(__dirname, "..", "build")));
app.use(express.static("build"));
app.listen(5000, () => {
console.log("server started on port 5000");
});
Now in your packages.json of the client react app change the name of start tag under scripts tag to start-client. Then add this following tag to the scripts tag:
"start":"npm run build && (cd server && npm start)",
Basically, this will build the react app and start the server.
It should look like this :
Also in the packages.json of your server add the following tag under script tag
"start":"node server.js"
So when you run the following command npm start it should look like this :

How to connect React SPA with NodeJS?

Folks I would like to ask for a resolution for this use case if it's possible or not.
Use Case
The functionality currently is that both React and Node code are in the same repo. Where we build the React and the build is placed in /public/ directory. NodeJS loads the static files from the /public/ directory and serve the SPA.
The required functionality is that:
React SPA build will live on server A
NodeJS build will live on server B
How can I serve the static files located in Server A from Server B?
Add this line to your server code (assuming you are using expressJS).
Also I would call name react project client/ instead of serverB.
// file:<project-root-dir>/server/main.js
const STATIC_FILES = path.join(__dirname, '../client/', 'build') // feel free to change `client` to `serverB`
//..
const app = express()
//....
if (process.env.NODE_ENV === 'production') {
app.use(express.static(STATIC_FILES))
app.get('/*', (_, res) => res.sendFile(path.join(STATIC_FILES, 'index.html')))
}

Express server and NGINX proxy only serving index.html, no other files

I have a very simple express app which serves everything in the build folder for my react app. Here's the entire thing:
const express = require("express");
require("dotenv").config();
const app = express();
const port = process.env.PORT || 5000;
app.use(express.static(process.env.PUBLIC_DIR));
app.use(express.json());
app.listen(port, () => {
console.log(`Server is running on port ${port}...`);
});
When running this on my local machine, it works fine. No issues.
On my EC2 instance, I'm using NGINX as a reverse proxy. Here's what the config in my default sites-available file looks like:
location / {
proxy_pass http://localhost:5000/;
}
location /upvotes {
proxy_pass http://localhost:5002/;
}
When you just go to the main site, another express app on 5000 serves a totally unrelated Gatsby project. That works fine, no issues.
When you go to /upvotes, this express app on 5002 does serve the index.html file perfectly fine, but it doesn't serve any of the accompanying .js and .css files that are also in that directory.
Does anyone know why this could be happening?
I eventually gave up and combined the two express apps into one and handled the /upvotes route using express. 🤷

How to server nodeJS / react app from base location?

I am trying to configure a nodeJS express app which handles a react static build to run from a location inside my VPS (nginx).
So far I have managed to configure the location like this:
added path in webpack build:
output: {
path: path.join(__dirname, '/client/public/dist/'),
filename: 'main.js',
publicPath: "/location/",
},
added the path to the express server:
app.use("/location/", express.static(path.join(__dirname, './client/public/dist/')))
added the location to the react router, to each route
added the location to the nginx proxy_pass:
location /location{
proxy_pass http://localhost:5002/location;
}
The issue I am facing is that if I have a subpath (e.g. /location/page1), if I refresh the browswer I get the error cannot get /location/page1
I have tried to modify the express server as app.use("/location/*", ) or app.use("*"),
but the I get Uncaught SyntaxError: Unexpected token '<'
Is this an issue with index.html? Is there any simpler way to run the app from a location route?
location /location/ {
rewrite ^/location/(.*) /$1 break;
proxy_pass http://localhost:5008/yourPath/;
}
Do not add * path in express, but add the path like above as I have shown in your Nginx file, I have tried this in Nginx itself for my projects.
Here /location/ can be anything of your choice where users can see but in the proxy pass, it should be exactly where you are serving through your application, for example, the route facing the user i.e /location/ can be /test/ that's what users see but after that, the routes can be your /location from where your application is served.
Do let me know if you have ambiguity in understanding the solution I can show you a demo and explain. Thank you.

Error while serving two node server based frontend via reverse proxy

I've a single domain and hence need to serve two different react applications running on two different node servers via nginx reverse proxy. Here is my nginx.conf file
server {
listen 80;
listen [::]:80;
client_max_body_size 128g; # allows larger files (like videos) to be uploaded.
location / {
proxy_pass http://192.168.0.105:8081;
}
location /admin {
proxy_pass http://192.168.0.105:8080;
}
}
What I am trying to achieve here is that if user lands on abc.com then he will be served react application from http://192.168.0.105:8081 while if user visits abc.com/admin he is served http://192.168.0.105:8080.
Both applications have different node servers serving react's bundle created by create-react-app. The node's server.js looks like this for both applications
const express = require("express");
const bodyParser = require("body-parser");
const path = require("path");
const cors = require("cors");
const app = express();
const qs = require("querystring");
const axios = require("axios");
const PORT = process.env.PORT || 8080;
const serverEnvironment = process.env.serverEnv || "dev";
app.use(cors());
app.use(express.static(path.join(__dirname, "build")));
app.use(bodyParser.json());
app.get("*", function(req, res) {
console.log("Requesting Admin UI");
res.sendFile(path.join(__dirname, "build", "index.html"));
});
app.listen(PORT, () => {
console.log(
`\nServer is being served in ${serverEnvironment} environment at port ${PORT}`
);
});
Everything works fine when I visit localhost:4000(4000 is port on which nginx is running) it serves me my expected portal. But when I visit localhost:4000/admin instead of serving admin application it ends up giving following error
Uncaught SyntaxError: Unexpected token '<' 2.0f3deea8.chunk.js:1
Uncaught SyntaxError: Unexpected token '<' main.a2e30dbb.chunk.js:1
When I checked network tab all the chunks of js and css are returning HTML instead of relevant js or css.
I also tried adding
location ~ .(static)/(js|css|media)/(.+)$ {
try_files $uri $uri/ /$1/$2/$3;
}
to nginx.conf but of no use. Am I missing something over here or is it not the correct way to reverse proxy 2 applications?
Are you seeing the content of index.html for your js/css in your network tab?
If yes, then it is usually caused by wrong base href in your index.html.
Try changing it to root.
<base href="/">
Other possible solution might be just adding this line to your nginx config.
include /etc/nginx/mime.types;
If you have base url /admin in node app too then proxy_pass should be to http://192.168.0.105:8080/admin; instead of http://192.168.0.105:8080;
Ideal scenario you can have your compressed react resource served from nginx at port 80 by copying it to html or any desired folder and just the different node apps proxied to different routes.
I would simply use a rewrite in your proxy location like:
server {
listen 80;
location / {
proxy_pass http://192.168.0.105:8081;
}
location /admin {
rewrite /admin/(.*) /$1 break;
proxy_redirect off;
proxy_pass http://192.168.0.105:8080;
}
}
With this configuration a user visiting /admin will be send to your admin ReactJS Application. The NGINX will rewrite the uri before submitting it to the upstream server.

Resources