Serve react app with express with special route - reactjs

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

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.

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

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"
}
}

404 Not Found nginx React

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

Resources