I'm trying to deploy a MERN app (built with create react app) to Heroku, but whenever I try to access the app URL, it returns with a 404 error.
I've checked the Heroku error log, which has returned the following errors:
app[web.1]: ls: cannot access '/app/build/static/js/*.js': No such file or directory
Error injecting runtime env: bundle not found '/app/build/static/js/*.js'. See: https://github.com/mars/create-react-app-buildpack/blob/master/README.md#user-content-custom-bundle-location
I've structured my project so that it runs on two different servers: client side on localhost:3000, which proxies requests to express at localhost:5000.
I've run npm run build, set up static middleware, and tried to configure my api calls/routes correctly, but it still isn't working. Any suggestions as to why, and how I can fix it? Details as follows:
Project Structure
+client
|
+-build
+-static
+-css
+-js
+-media
+-node_modules
+-public
+-src
|
+-components
+-App.js
+-index.js
//server
+-models
+-node-modules
+-package-lock.json
+-package.json
+-server.js
Proxy (in package.json):
"proxy": "http://localhost:5000"
Heroku build scripts (in client/package.json):
"scripts": {
"start": "react-scripts start",
"heroku-postbuild": "cd client && npm install --only=dev && npm install && npm run build",
Server config:
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Listening on port ${port}`));
//Middleware
app.use(express.static(path.join(__dirname, 'client/build')));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.urlencoded())
app.use(cors())
app.get('*', (req,res) =>{
res.sendFile(path.join(__dirname+'/client/build/index.html'));
});
Here's how I;ve structured my APIs. Note: I've removed the 'localhost:5000' from the URL of my axios requests:
API call from React component:
useEffect(() => {
axios.get('/api/all-reviews')
.then(review => {
setReviews(review.data)
})
.catch(err => {
console.log(err)
})
},[])
Corresponding express route
app.get('/api/all-reviews', (req,res) => {
Review.find()
.then((result) => {
res.send(result)
})
.catch(err => {
console.log(err)
})
})
You have two options,
#1 - make all urls relative, e.g. fetch('/api/all-reviews'), and have both the frontend and backend running on the same server. Serve the static build files from your backend (found in the build folder after running npm run build, assuming you are using create-react-app) using the express.static middleware.
Note that you can do this in production while still relying on a proxy in development using process.env.NODE_ENV. An example implementation would be
// put this at the end of your server.js file
if (process.env.NODE_ENV === 'production') {
app.use(express.static(path.join(__dirname, '../client/build')));
}
#2 - run the backend and frontend on different servers, and just adjust the path based on whether the code is running in development or production
Just as an example:
const prefix = process.env.NODE_ENV === 'production' ? "http://heroku_app_address" : "http://localhost:5000"
function getUrl(relativeUrl) {
return prefix + "/" + relativeUrl;
}
fetch(getUrl('api/all-reviews'));
Related
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.
So basically i have deployed a react application on docker using express to serve my build folders. But the after i deployed the application only opens when i follow the url with "//".
I have set the homepage property of package.json to "."
The main js chunk will not load if i just type the application URL . It will load only if the application url is followed by double slash for example https://example.com//
Can any one please make me understand why might this behavior occur? Following is the code for the server.js
const path = require("path");
const express = require("express");
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.static(path.join(__dirname, "..", "build")));
app.use(express.static("public"));
app.use((req, res, next) => {
res.sendFile(path.join(__dirname, "..", "build", "index.html"));
});
app.listen(PORT, () => {
console.log(`Server started on PORT ${PORT}`);
});
Dockerfile
FROM node:16 AS ui-build
WORKDIR /usr/src/app/
COPY . .
RUN npm install && npm run build
EXPOSE 3000
ENV PORT 3000
ENTRYPOINT ["node", "server/server.js"]
package.json
{
"name": "app",
"version": "0.1.0",
"homepage": ".",
"private": true,
"dependencies": {...
}
This is a personal portfolio page that I'm implementing a contact form within, using nodemailer.
The nodemailer thing is all set from server side. I just need some advice on pointing the client post request to the right place in regards to development and deployment.
I figured as much for setting an environment variable for production vs development and hitting the fetch based upon that. Now I'm just wondering how to go about finding whatever I would put in the fetch for production.
would it be just pointing back into my own app:
fetch(www.mydomain.com/send-email, data) ...
I'm in the Heroku docs trying to figure this out.
Basically, I have a huge blind spot which is hitting a server API from Create React App that isn't launched independently on localhost:3000. I have yet to hit a server route from my client that wasn't served locally on localhost. When I push this to Heroku, I need to have the right route or config, what I need is some advice on how to do this.
I understand proxying somewhat. Just wondering what the steps are to properly hit my server route from an client/server deployed on Heroku as opposed to localhost:3000 during deployment.
When I'm in development I pretty much always axios.post a server that I've spun up on localhost:3000,
which I then hit with something like this coming from my client..
axios.post('localhost:3000/send-email', data)
.then( () => {
setSent(true)
})
.then(() => {
resetForm()
})
.catch((err)=> {
console.log('Message not sent', err)
})
}
...which is then handled by an endpoint on the express server listening on localhost:3000, that looks somewhat like what I've pasted below.
const express =
require('express'),
bodyParser = require('body-parser'),
nodemailer = require('nodemailer'),
cors = require('cors'), path = require('path'),
port = process.env.PORT || 3000, publicPath = path.join(__dirname, '..', 'build');
require('dotenv').config();
const app = express();
app.use(cors());
app.use(express.static(publicPath));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.get('*', (req, res) => {
res.sendFile(path.join(publicPath, 'index.html'));
});
app.post('/send-email', (req, res) => {
console.log('request: ', req.body)
let data = req.body;
let transporter = nodemailer.createTransport({
service: 'gmail',
port: 465,
auth: {
user: process.env.EMAIL,
pass: process.env.PASSWORD
}
});
let mailOptions = {
from: data.email,
to: process.env.EMAIL,
subject: `${data.subject}`,
html: `<p>${data.name}</p>
<p>${data.email}</p>
<p>${data.message}</p>`
};
transporter.sendMail(mailOptions,
(err, res) => {
if(err) {
res.send(err)
} else {
res.send('Success')
}
transporter.close();
});
})
app.listen(port, () => {
console.log(`Server is up on port ${port}!`);
});
folder structure is like this:
main
|-server
|-server.js
|-src
|-components
|-Contact.js
Use the process.env.NODE_ENV variable to differ the environments.
When you run npm start, it is always equal to 'development', when you run npm test it is always equal to 'test', and when you run npm run build to make a production bundle, it is always equal to 'production'. You cannot override NODE_ENV manually.
Therefore, you can create and export a function like
export function apiDomain() {
const production = process.env.NODE_ENV === 'production'
return production ? 'anotherDoman' : 'localhost:3000'
}
or maybe, depending on your requirements
export function apiDomain() {
const { protocol, hostname, origin } = window.location
return hostname === 'localhost' ? `${protocol}//${hostname}` : origin
}
For more details, take a look at https://create-react-app.dev/docs/adding-custom-environment-variables/
I have an express server deployed on Heroku: https://server.mydomain.com
and a Next.js React app also deployed on Heroku: https://app.mydomain.com
Both have their SSL certificates automatically configured by Heroku, and when I visit the https domains, they work as expected.
The problem I have is that when I visit http://app.mydomain.com, it does not redirect to https://app.mydomain.com.
All the solutions I've found online point to forcing SSL on the server:
this popular question says to check for the x-forwarded-proto value:
/* At the top, with other redirect methods before other routes */
app.get('*',function(req,res,next){
if(req.headers['x-forwarded-proto']!='https')
res.redirect('https://app.mydomain.com'+req.url)
else
next() /* Continue to other routes if we're not redirecting */
})
and others suggest using a package like express-sslify or heroku-ssl-redirect.
These solutions work fine for the server requests, but loading a React client page does not necessarily trigger app.get(). Obviously, a React client can run independently of a server.
So the question is: How does someone force https for a subdomain Next.js React client app on Heroku? Without using express server methods?
I do this in one of my production applications.
We prepare the next app object and init an express server. This is done in the server.js file. You can read more about it in the docs about a custom server.
Next.js also has an example in their github in the examples folder about a custom express server. It's here.
const express = require('express');
const next = require('next');
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
app
.prepare()
.then(() => {
const server = express();
server.use((req, res, next) => {
const hostname = req.hostname === 'www.app.domain.com' ? 'app.domain.com' : req.hostname;
if (req.headers['x-forwarded-proto'] === 'http' || req.hostname === 'www.app.domain.com') {
res.redirect(301, `https://${hostname}${req.url}`);
return;
}
res.setHeader('strict-transport-security', 'max-age=31536000; includeSubDomains; preload');
next();
});
server.get('*', (req, res) => handle(req, res));
server.listen(
4242,
error => {
if (error) throw error;
console.error('Listening on port 4242');
}
);
})
.catch(error => {
console.error(error);
process.exit(1);
});
As for deploying to Heroku you should be able to just customize the npm start script to start nextjs like so:
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
Heroku also runs npm run build automatically so it should build the app for you.
Heroku does not currently "offer out of the box" functionality to force the use of https for node apps.
However, with the release of Nextjs v12 you can accomplish this without having to setup a custom server and use middleware instead.
See this answer for example code and advantages of middleware vs custom server.
I also published a npm package to handle this:
import sslRedirect from 'next-ssl-redirect-middleware';
export default sslRedirect({});
This is my first time serving react files with express.js. The build has been run and the server is listening, but I cant figure out why the components aren't being injected into the html file. Instead it's rendering just the html template.
Here is a picture of my build folder:
build folder structure
Here is my index.js file from my server folder:
const express = require('express');
const morgan = require('morgan');
const path = require('path');
const app = express();
app.use(morgan(':remote-addr - :remote-user [:date[clf]] ":method
:url HTTP/:http-version" :status :res[content-length] :response-time
ms'))
app.use(express.static(path.resolve(__dirname, '..', 'build')))
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname,'..', 'build', 'index.html'))
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`App listening on port ${PORT}!`);
});
this what I see in the console:
served page console
Any help would greatly be appreciated.
This question is still unanswered and Im facing the same question. I have deployed the default react app to a build-directory and am hoping to serve the files through Express.
The browser actually received the deployed file BUT it shows me a blank page.
Any ideas?
Using a simple Express server:
const express = require("express");
const path = require("path");
const app = express();
app.use(express.static(".\build"));
app.get("*", (req, res) => {
res.sendFile(path.resolve(__dirname, ".", "build", "index.html"));
});
app.listen(8081, () => {
console.log(App listening on port 8081!);
});
See the following link
react-express-boilerplate
Add the following code to index.html file
<script src="src="/js/main.33e13313.js""></script>
main.33e13313.js is a file that packages js with webpack.
Your package.json file will be set as follow:
"scripts": {
"clean": "rm -rf build public/bundle.js",
"build": "babel server --out-dir build && webpack",
"start": "NODE_ENV=production supervisor ./build/main.js",
"development": "NODE_ENV=development node ./build/main.js"
},
You will use npm run build to run.
You can use babel to change es6 to es5. this is build tool.
You can use webpack to package JavaScript files. packagig is bundles JavaScript files into one file. this is packaging tool.