404 Not Found nginx React - reactjs

I have a react app and i serve it using express. It work well locally but when i deploy it to heroku i get this error 404 Not Found.
The error is when i open for example this link stand alone on a separate browser window :
http://localhost:9000/projects/5a0df094c27078039346fee2
it works very well. But if i deploy to heroku and then i try the same call i get 404.
If i navigate internally i don't get 404 but this happen only if i open it on a separate window in the browser and its the heroku one .
http://www.mywebsite.website/projects/5a0df05dc27078039346fedf
In my express server i have this code:
// server/app.js
const express = require('express');
const morgan = require('morgan');
const path = require('path');
const app = express();
// Setup logger
app.use(morgan(':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] :response-time ms'));
// Serve static assets
app.use(express.static(path.resolve(__dirname, '..', 'build')));
app.get('/projects/:ProjectId', (req, res) => {
res.sendFile(path.resolve(__dirname, '..', 'build', 'index.html'));
});
app.get('/projects/', (req, res) => {
res.sendFile(path.resolve(__dirname, '..', 'build', 'index.html'));
});
// Always return the main index.html, so react-router render the route in the client
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, '..', 'build', 'index.html'));
});
app.use(function(req, res, next){
res.status(404);
// respond with html page
if (req.accepts('html')) {
res.render('404', { url: req.url });
return;
}
// respond with json
if (req.accepts('json')) {
res.send({ error: 'Not found' });
return;
}
// default to plain-text. send()
res.type('txt').send('Not found');
});
module.exports = app;
And for my react app i have this code.
<Switch>
<AppliedRoute path="/" exact component={AsyncHome} props={childProps} />
<AppliedRoute
path="/projects/:ProjectId"
exact
component={AsyncSingleProject}
props={childProps}
/>
{/* Finally, catch all unmatched routes */}
<Route component={AsyncNotFound} />
</Switch>

dont know about deployment in heroku, but i have tried deploying in public ip and got the same issue, I think its because routing is working properly in localhost but not working during deployment with nginx.
I have edited the "default" file which is in /etc/nginx/sites-available/default in my machine nginx/1.4.6 (Ubuntu)
sudo gedit /etc/nginx/sites-available/default
Inside the server you can find location, edit that and change to
server{
.......
location / {
try_files $uri /index.html;
}
........
}
Its simple. This worked for me. Hope this will help someone.
Thanks

Related

Serve react app with express with special route

I need to get access to my react app build from an express back-end from '/app' route
Folder structure:
src > entrypoint.ts
client > build > index.html
This code below works fine, but i can access to app by '/' route, thus this don't actually i need.
app.use(express.static(path.join(process.cwd(), '/client/build')))
app.get('*', (req, res) => {
res.sendFile('index.html', { root: path.join(process.cwd(), './client/build') })
})
I need sth like this:
app.use('/app', express.static(path.join(process.cwd(), '/client/build')))
app.get('/app', (req, res) => {
res.sendFile('index.html', { root: path.join(process.cwd(), './client/build') })
})
I tried this, but got an error:
Refused to execute script from because its MIME type ('text/html') is not executable

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.

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

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

Why cannot I redirect my React app on Heroku from http to https?

I have an app on Heroku that was created using create-react-app. Just today, I got an SSL cert using Heroku's automated(-ish) SSL cert process ExpeditedSSL, and the documentation then suggests rerouting all http requests to https.
I have a server.js file and express I use just to attempt to run middleware and then serve my React app.
I know the SSL cert is working as if I go to https://myapp.com I see my Heroku app, but when I go to http://myapp.com it is not redirected to the https version of my Heroku app.
I have tried many, many solutions today from StackOverflow, Google, and otherwise and none of the the solutions have worked for me. I don't get any errors, either. It just doesn't work.
Attempt using https library:
const https = require("https");
const express = require('express');
const app = express();
app.use(express.static(path.join(__dirname, 'build')));
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
https.createServer(app).listen(3000);
Another attempt using heroku-ssl-redirect:
var sslRedirect = require('heroku-ssl-redirect');
var express = require('express');
var app = express();
// enable ssl redirect
app.use(sslRedirect(['production'], 301));
app.use(express.static(path.join(__dirname, 'build')));
app.get('*', (req, res, next) => {
if (req.headers['x-forwarded-proto'] != 'https'){
res.redirect('https://' + req.hostname + req.url);
} else {
next();
}
});
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
app.listen(process.env.PORT || 3000);
Attempt using x-forward-proto:
const express = require('express');
const env = process.env.NODE_ENV || 'development';
const bodyParser = require('body-parser');
const path = require('path');
const app = express();
var forceSsl = function (req, res, next) {
if (req.headers['x-forwarded-proto'] !== 'https') {
return res.redirect(['https://', req.get('Host'), req.url].join(''));
}
return next();
};
if (env === 'production') {
app.use(forceSsl);
}
app.use(express.static(path.join(__dirname, 'build')));
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
app.listen(process.env.PORT || 8080);
I've also attempted a number of random node installs from various blogs and SO posts, and nothing has worked. Without any errors I am having a hard time figuring out why this doesn't work.
Try adding the following to the header of your index.html file
<script>
var host = "www.URL.com" || "URL.com";
if ((host == window.location.host) && (window.location.protocol != "https:")){
window.location.protocol = "https";
}
</script>
I was dealing with a similar issue and this allowed me to resolve it. Placing it in the header ensures that the script will run before any of your bundled JavaScript, but I suppose it would work above your bundle.js file
UPDATED and SECURE solution below:
Since you are deploying your react app via Heroku using create-react-app and it's buidlpack (recommended, if you are not: create-react-app-buildpack). So as the official docs says:
The web server may be configured via the static buildpack.
The config file static.json should be committed at the root of the repo. It will not be recognized, if this file in a sub-directory
The default static.json, if it does not exist in the repo, is:
{
"root": "build/",
"routes": {
"/**": "index.html"
}
}
HTTPS-only
Enforce secure connections by automatically redirecting insecure requests to https://, in static.json:
{
"root": "build/",
"routes": {
"/**": "index.html"
},
"https_only": true
}
The method by #misterfoxy works but is not the right way!
Try using express-https-redirect.
Install with:
npm install express-https-redirect --save
Then you should be able to do something like:
const express = require('express');
const httpsRedirect = require('express-https-redirect');
const app = express();
app.use('/', httpsRedirect());
app.set('port', process.env.PORT || 3000);
app.use(express.static(path.join(__dirname, 'build')));
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
app.listen(app.get('port'), function () {
console.log('Express server listening on port ' + app.get('port'));
});
Try adding this to your index.html file
Place it in the header, so it would run before other scripts
<script>
const domain1 = "www.example.com";
const domain2 = "example.com";
const host = window.location.host;
if ((domain === host || domain2 === host) && (window.location.protocol !== "https:")){
window.location.href = "https://www.example.com";
}
</script>
This seem to work for me React on Heroku
Buildpack
https://github.com/mars/create-react-app-buildpack
root/static.json
{
"root": "build/",
"routes": {
"/**": "index.html"
},
"https_only": true
}
https://youtu.be/LAjj_kzqbjw

Resources