0
votes

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)
    })
})
1
Are your frontend and backend running on the same or different servers? (i.e. does your backend serve your frontend as static files?) - Cirrus86
They're running on different servers: client side on port 3000, and then server side on 5000. I've set up my project so that all client side requests are proxied to the server on port 5000, though. - Josh Simon

1 Answers

1
votes

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'));