How does express serve index.html for React App and how do I modify it? - reactjs

I'm an express noob here and building a React App with server using express and client using create-react-app.
What I want to do
I want to update the title and meta tag in the index.html.
So browser requests url -> Server gets request and adds the title and tag to the index.html -> return it to the browser.
Listed my code here
...
app.use(bodyParser.json())
app.use(aMiddleware)
app.use("/api/foo", bar)
app.use(express.static('client/build'));
if (process.env.NODE_ENV === 'production') {
const path = require('path');
app.get('/*', (req, res) => {
res.sendFile(path.resolve(__dirname, '../client', 'build', 'index.html'))
})
}
Questions
Code is functioning, but I don't know how to replace the title/tag in the index.html
How do I update/replace index for environment that is not prod?
Fo prod environment, I use path.resolve(__dirname, '../client', 'build', 'index.html'), then where is index.html for dev environment? I see there is an index.html in public folder, is it the one that got rendered for dev environment?
I tried to add this code before app.use(express.static(...))
app.get('/', function(req, res) => {
// maybe replace string in the index.html (though I don't know where is it
// then res.send(...)?
})
but this never got triggered. Why?
Stuck on this for a while, tried many things, any help would be great.

You can use react-helmet for this... Or switch to Nextjs which is server side.
https://www.npmjs.com/package/react-helmet

Related

After deployment getting 404 error axios post

Currently, I'm trying to get axios data from node.js. and i can get the result on local url , however after i build it and deploy it, the post method get 404 error. so i tried to use get method to test it. it gets react html result .
it's totally okay when i do it on local. but only it doesn't work when i build and deployment.
I assumed it's because proxy problem so i installed http-proxy-middleware library and
I try to set up setupProxy.js on my react folder.
this is the example from
"https://create-react-app.dev/docs/proxying-api-requests-in-development/"
but it still doesn't work.
i want to know what can make this issue.
//node.js
app.get("/test", (req, res) => {
res.send({ hello: "Hello world" });
});
const __dirname = path.resolve();
app.use(express.static(path.join(__dirname, "dist")));
app.get("/*", (req, res) => {
res.sendFile(path.join(__dirname, "dist", "index.html"));
});
//react
const getTest = () => {
axios
.get(`${backend}/test`)
.then(res => {
console.log(res.data);
})
.catch(err => console.log(err));
};
The proxy configuration only applies to the webpack-dev-server you use when developing your React app...
Keep in mind that proxy only has effect in development (with npm start), and it is up to you to ensure that URLs like /api/todos point to the right thing in production.
I would suggest just using the simpler version by adding this to your package.json
"proxy": "http://localhost:5000",
You should also make sure your Express app is configured to handle API requests in both dev and production modes. To do so, I'd recommend using the same routes as used in the requests from the front-end...
app.get("/api/test", (req, res) => {
res.send({ hello: "Hello world" });
});
// or even better
app.use("/api", myApiRouter);
Now your React app can make requests to /api/test in both development and production modes
axios.get("/api/test").then(({ data }) => console.log(data));
In development mode, the proxy configuration will forward the requests to your Express app.
In production mode, your Express app will be serving the built React app via express.static() so they'll be on the same domain.

React-router urls don't work when refreshing or writing manually, gives 404 page not found

My production webiste opens normally, but for a user visiting/accessing that link the first time, he gets a 404 Page Not Found. For users that has already visited the website it loads just fine.
This is the url - https://literacycloud.org/readaloudsdetail/546-jennifer-phillips-reads-the-invitation.
Ideally it should redirect to the login page, but there is no api being hit at all.
The issue doesn't get reproduced locally only when deployed to development or to live, getting this issue.
Is it something back-end has to handle from their end?
Any help appreciated.
Set historyApiFallback in your webpack.config.js. Checkout this.
Also, it is a good idea to serve your index.html from a server. The idea is that no matter what url you visit on your domain, the server should always return the index.html as it contains your react code.
Here is a sample code:
const path = require('path');
const express = require('express');
const app = express();
app.use(express.static(path.join(__dirname, 'build')));
app.set('port', process.env.PORT || 8080);
app.get("*", (req, res, next) => {
const filePath = path.join(__dirname, 'build', 'index.html');
res.sendFile(filePath);
});
const server = app.listen(app.get('port'), function () {
console.log('listening on port ', server.address().port);
});

Plesk/React/Express - Starting point is (React) index.html. But I can't do a backend request

BIG EDIT SINCE I DID SOME MORE RESEARCH
I'm trying to deploy my first Nodejs/React App on a Cloud-Server using Plesk.
That's what I tried first:
I created an .httaccess file with the following contents.
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.html [QSA,L]
Problem with this was, that I didn't have access to express app.js anymore, since react's index.html file handles everything. So the alternative is to route accordingly out of app.js from express. I have found the following approach and trie to implement it.
Approch:
/api/app.js
app.use('/result', resultRouter);
app.use(express.static(path.join(__dirname, 'build')));
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
app.get('/*', function (req, res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
My implementation:
var createError = require('http-errors');
var express = require('express');
const cors = require('cors');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
require('dotenv').config();
var helmet = require('helmet');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var resultRouter = require('./routes/result');
var app = express();
app.use(helmet());
app.use(cors());
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
next();
});
//Set up mongoose connection
var mongoose = require('mongoose');
var mongoDB = 'MYMONGODBURL';
mongoose.connect(mongoDB, { useNewUrlParser: true, useUnifiedTopology: true });
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'MongoDB connection error:'));
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use('/result', resultRouter);
app.use(express.static(path.join(__dirname, 'build')));
app.get('/', function (req, res) {
res.sendFile(path.resolve(__dirname, 'build', 'index.html'));
});
app.get('*', function (req, res) {
res.sendFile(path.resolve(__dirname, 'build', 'index.html'));
});
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
While I am sure this is the correct solution in general, I fail to implement it. The app.js file lays in the api folder. Before uploading it to Plesk, I insert the build folder from react inside. So app.js and the build folder are on the same level of the directory.
After the upload via git, I have both the document root as well as the application root set to configurator/api. Configurator is just an empty folder I set up beforehand.
If I set document root to configurator/api/build, the starting page of my react index.html file gets displayed. But routing to any other react component doesnt work.
What do I do wrong here? Also I have enabled "browsing for a domain", but still get a 504 Gateway Timeout Error.
I hope someone can point me in the right direction or has some input as to where I should look next.
Thanks in advance
Found a solution to my problem.
So I know my question is on a real beginner level and in hindsight, I didn't give enough info for someone to help me out here. But I found a solution, that hopefully helps other beginners that are stuck at this point. So I write to share what I learned in the process and also to reflect on it.
2 ways to get react/express running on Plesk
Basically there are two ways to get react/express running on Plesk. Both solutions are viable, but tackle different prerequesites.
Solution 1: You want to render a static site via react, that doesnt perform any backend-requests to nodejs.
In this case, you run Çıpm run build in your react-folder and put the newly created build folder inside your express folder. Inside the build folder, create a .htaccess file with the following content:
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.html [QSA,L]
I think doing it this way, you don't even need nodejs. But I haven't tried it out. Anyway. Upload the folder (let's call it api) to Plesk via git and set document root to api/build, while application root is just api.
Solution 2: You want to render static react, but you perform backend-requests to express for some business logic or database-requests at any point.
As in solution 1, create the build folder and move it into the api folder. The build folder is everything you need from React for your app to work at the end. In the api folder, create a new Javascript file on the root level. Name it server.js. Inside put the following code:
const app = require('./app');
const http = require('http');
http.createServer(app).listen(process.env.PORT);
As I understood, this starts your server in the first place and without you'll get a 504 Timeout-Error.
Second, you need to tell nodejs, that it redirects to react's index.html file, whenever you hit a route, that is not defined in express. For this, open your app.js file. Go right under the last of your express routes and insert the following:
app.use('/result', resultRouter);
// ^ above is the last route of my express app.
// below tells your server to redirect all other routes to index.html from React
app.use(express.static(path.join(__dirname, 'build')));
app.get('/', function (req, res) {
res.sendFile(path.resolve(__dirname, 'build', 'index.html'));
});
app.get('*', function (req, res) {
res.sendFile(path.resolve(__dirname, 'build', 'index.html'));
});
After modifying and uploading to Plesk, set both your document root and your application root from the Plesk-Nodejs application to api (or whatever your root-folder is). Then, set the application starting file to server.js. Everything should work now.
Troubleshooting
Here are some obstacles I had on the way and you might face also.
Content Security Policy Error: After setting everything up successfully with solution 2, I got a Content Security Policy Error. This was because of the middleware Helmet I use in my express-app. Comment helmet out to test if it's the problem. If it is, there are ways to setup Helmet correctly. You don't need to disable it really.
CORS Error: My backend request failed with the same origin policy being hurt. This was because the axios request from react to express still referred to localhost. Replace all axios request urls in React with the right url from your production-domain.

I would like to alter the way create-react-app builds my app

So I would like to make npm run build to produce local runnable files. Basically, I want it to build index.js with importing assets like ./static/js/chunk.js instead of /static/js/chunk.js. The client I am running this on does not have internet access so I want to serve the app from the file system.
Basically, I want to open index.html in the build folder with chrome and the app just work. Any ideas on how to do this?
Using simple node js and serve it as static is the best way because react router dom need url in the app.
const express = require("express");
const path = require("path");
const app = express();
app.use("/static", express.static());
app.get("*", (req, res) => {
res.sendFile(path.resolve(__dirname, "frontend", "build", "index.html"));
});
app.listen(6000, () => console.log("Listening to port 6000"));
Next to node file index.js, or whatever you name it, make the folder called frontend. Inside it put your build from react so the node can serve index.html in port 6000

How do I rewrite all urls to index.html in Heroku?

My Heroku app is using React with React Router. I use Switch to navigate through different components, so the URL changes as well (e.g. /room/4141). However, if I reload the page, it doesn't act like if it was a React app, but instead it searches for the mentioned .html file.
I used this Buildpack: https://github.com/mars/create-react-app-buildpack.git but it seems to do nothing in regards with pages being rewritten to index.html.
Is there a way to prevent this behaviour and rewrite all URLs to index.html?
**EDIT:
I'm not familiar enough with express, but here's how the index.html is served.
const express = require("../../node_modules/express");
const app = express();
const server = require("http").Server(app);
const io = module.exports.io = require('../../node_modules/socket.io/lib')(server)
const path = require("path")
app.use(express.static(path.join(__dirname, '../../build')));
if(process.env.NODE_ENV === 'production') {
app.use(express.static(path.join(__dirname, '../../build')));
console.log("DEBUG HERE", __dirname, path.join(__dirname+'../../build'));
//
app.get('/*', (req, res) => {
res.sendFile(path.join(__dirname+'../../build/index.html'));
})
}
//build mode
app.get('/*', (req, res) => {
res.sendFile(path.join(__dirname+'../../public/index.html'));
})
That buildpack can be configured via a JSON file:
You can configure different options for your static application by writing a static.json in the root folder of your application.
One of the sample routing configurations looks like it does exactly what you want:
When serving a single page app, it's useful to support wildcard URLs that serves the index.html file, while also continuing to serve JS and CSS files correctly. Route ordering allows you to do both:
{
"routes": {
"/assets/*": "/assets/",
"/**": "index.html"
}
}

Resources