0

Summary of the issue:

When deploying to Heroku a flask/react application, I'm having trouble running two buildpacks at once and making the application function. I typically am encountering 1 of 2 issues depending on how I setup the project.

  1. If I instruct Heroku to cd static && npm --dev install && npm run build:production in package.json postinstall script, my bundle.js file can't be found and all my components are reportedly not accessible:

Here is an example error I get from Heroku in the terminal after a successful build:

ERROR in ./src/containers/HomeContainer/index.js
remote: Module not found: Error: Cannot resolve 'file' or 'directory'      ../../components/IntroSection in /tmp/build_d95bc1f5e53719f4bd91a1a3/static/src/containers/HomeContainer
remote: resolve file
  1. If I instruct Heroku to cd static && npm --dev install && npm run build:production && npm start I see the the application builds properly and attempt to listen on the server port. However, eventually the terminal times out and never finished it seems?

Here's how it looks in the terminal towards the last command:

remote:        > node bin/server.js
remote:        
remote:        Listening on: {"address":"::","family":"IPv6","port":8080}
remote:        webpack built e95b297d680022fe in 23191ms

The terminal remains stopped here.


In both instances, I can access the python side of the application no problem. The issue is getting the client side (react/redux) to render. When running this all locally in either version, it functions perfectly fine. No component errors and the server starts right up. By the way, I was told that Heroku needs package.json in root to help it run the setup. As a result, I've now got 2 package.json files. One in root and one in static. The one in root is just to help it download node/npm and push it over to the other package.json file (I think?).

Here's some detail I can give to try to debug the problem further:

-- Procfile:

web: gunicorn main:app

-- Buildpacks on Heroku:

heroku buildpacks:set heroku/python
heroku buildpacks:add heroku/nodejs

--Package.json in root:

{
  "name": "something",
  "version": "0.0.1",
  "engines": { "node": "6.11.1", "npm": "3.10.10" },
  "scripts": {
    "postinstall": "cd static && npm --dev install && npm run build:production && npm start"
  }
}

-- Package.json in static:

{
  "name": "redux-easy-boilerplate",
  "version": "1.3.3",
  "description": "",
  "scripts": {
    "clean": "rimraf dist",
    "build": "webpack --progress --verbose --colors --display-error-details --config webpack/common.config.js",
    "build:production": "npm run clean && npm run build",
    "lint": "eslint src",
    "start": "node bin/server.js",
    "test": "karma start"
  },
  "repository": {
    "type": "git",
    "url": ""
  },
  "keywords": [
    "react",
    "reactjs",
    "boilerplate",
    "redux",
    "hot",
    "reload",
    "hmr",
    "live",
    "edit",
    "webpack"
  ],
  "author": "https://github.com/anorudes, https://github.com/keske",
  "license": "MIT",
  "devDependencies": {
    "autoprefixer": "6.5.3",
    "axios": "^0.15.3",
    "babel-core": "^6.4.5",
    "babel-eslint": "^7.1.1",
    "babel-loader": "^6.2.1",
    "babel-plugin-import": "^1.2.1",
    "babel-plugin-react-transform": "^2.0.0",
    "babel-plugin-transform-decorators-legacy": "^1.3.4",
    "babel-polyfill": "^6.3.14",
    "babel-preset-es2015": "^6.3.13",
    "babel-preset-react": "^6.3.13",
    "babel-preset-react-hmre": "^1.0.1",
    "babel-preset-stage-0": "^6.3.13",
    "bootstrap": "^3.3.5",
    "bootstrap-loader": "^1.2.0-beta.1",
    "bootstrap-sass": "^3.3.6",
    "bootstrap-webpack": "0.0.5",
    "classnames": "^2.2.3",
    "css-loader": "^0.26.4",
    "csswring": "^5.1.0",
    "deep-equal": "^1.0.1",
    "eslint": "^3.4.0",
    "eslint-config-airbnb": "13.0.0",
    "eslint-plugin-import": "^2.2.0",
    "eslint-plugin-jsx-a11y": "^3.0.1",
    "eslint-plugin-react": "^6.1.2",
    "expect": "^1.13.4",
    "exports-loader": "^0.6.2",
    "expose-loader": "^0.7.1",
    "express": "^4.13.4",
    "express-open-in-editor": "^1.1.0",
    "extract-text-webpack-plugin": "^1.0.1",
    "file-loader": "^0.9.0",
    "gapi": "0.0.3",
    "history": "^4.4.1",
    "http-proxy": "^1.12.0",
    "imports-loader": "^0.6.5",
    "jasmine-core": "^2.4.1",
    "jquery": "^3.1.0",
    "jwt-decode": "^2.1.0",
    "karma": "^1.2.0",
    "karma-chrome-launcher": "^2.0.0",
    "karma-mocha": "^1.1.1",
    "karma-webpack": "^1.7.0",
    "less": "^2.7.2",
    "less-loader": "^2.2.3",
    "lodash": "^4.5.1",
    "material-ui": "^0.16.4",
    "mocha": "^3.0.2",
    "morgan": "^1.6.1",
    "node-sass": "^3.4.2",
    "postcss-import": "^9.0.0",
    "postcss-loader": "^1.1.1",
    "q": "^1.4.1",
    "qs": "^6.1.0",
    "rc-datepicker": "^4.0.1",
    "react": "^15.3.1",
    "react-addons-css-transition-group": "^15.3.1",
    "react-bootstrap": "^0.31.0",
    "react-calendar-component": "^1.0.0",
    "react-date-picker": "^5.3.28",
    "react-datepicker": "^0.37.0",
    "react-document-meta": "^2.0.0-rc2",
    "react-dom": "^15.1.0",
    "react-forms": "^2.0.0-beta33",
    "react-hot-loader": "^1.3.0",
    "react-loading-order-with-animation": "^1.0.0",
    "react-onclickoutside": "^5.3.3",
    "react-redux": "^4.3.0",
    "react-router": "3.0.0",
    "react-router-redux": "^4.0.0",
    "react-tap-event-plugin": "^2.0.1",
    "react-transform-hmr": "^1.0.1",
    "redux": "^3.2.1",
    "redux-form": "^6.0.1",
    "redux-logger": "2.7.4",
    "redux-thunk": "^2.1.0",
    "resolve-url-loader": "^1.4.3",
    "rimraf": "^2.5.0",
    "sass-loader": "^4.0.0",
    "style-loader": "^0.13.0",
    "url-loader": "^0.5.7",
    "webpack": "^1.12.11",
    "webpack-dev-middleware": "^1.5.0",
    "webpack-dev-server": "^1.14.1",
    "webpack-hot-middleware": "^2.6.0",
    "webpack-merge": "^1.0.2",
    "yargs": "^6.5.0"
  },
  "dependencies": {
    "ant-design-pro": "^0.3.1",
    "antd": "^3.0.0",
    "lodash": "^4.17.4",
    "prop-types": "^15.6.0",
    "react-bootstrap": "^0.31.0",
    "redux-devtools-extension": "^2.13.2"
  }
}

Here's my current setup so you can understand how the build is being performed.

ROOT
├──/application
│   ├── models.py
│   ├── app.py
├──/static
│   ├──/bin
│   ├──/dist
│   │   ├──bundle.js
│   ├──/node_modules
│   ├──/src
│   │   ├──/actions
│   │   ├──/components
│   │   │   ├──/examplecomponenthere
│   │   │   │   ├──index.js (for example component)
│   │   ├──/constants
│   │   ├──/containers
│   │   ├──/reducers
│   │   ├──/store
│   │   ├──/webpack
│   ├──index.html
│   ├──package.json (the true one)
│   ├──server.js
├──/tests
├──config.py
├──index.py
├──main.py
├──package.json (one to help heroku start)
├──procfile
├──requirements.txt.
├──setup.py
├──tests.py

Here is my server.js file (unsure if this is needed):

const http = require('http');
const express = require('express');
const httpProxy = require('http-proxy');
const path = require('path');

const proxy = httpProxy.createProxyServer({});

const app = express();

app.use(require('morgan')('short'));

(function initWebpack() {
    const webpack = require('webpack');
    const webpackConfig = require('./webpack/common.config');

    const compiler = webpack(webpackConfig);

    app.use(require('webpack-dev-middleware')(compiler, {
        noInfo: true, publicPath: webpackConfig.output.publicPath,
    }));

    app.use(require('webpack-hot-middleware')(compiler, {
        log: console.log, path: '/__webpack_hmr', heartbeat: 10 * 1000,
    }));

    app.use(express.static(path.join(__dirname, '/')));
}());

app.all(/^\/api\/(.*)/, (req, res) => {
    proxy.web(req, res, { target: 'http://0.0.0.0:8081' });
});

app.get(/.*/, (req, res) => {
    res.sendFile(path.join(__dirname, '/index.html'));
});


const server = http.createServer(app);
server.listen(process.env.PORT || 8080, () => {
    const address = server.address();
    console.log('Listening on: %j', address);
    console.log(' -> that probably means: http://0.0.0.0:%d', address.port);
});

Conclusion One final note that on local I typically start the python side with a manage.py runserver script. Then I open up another terminal, cd into static and do npm start.

I'm so frustrated in trying to get heroku to work here for this multi buildpack. Any help would be so greatly appreciated! Even if it's a tutorial where I can learn more and start to dissect the problem or maybe I"m approaching this production setup incorrectly?

dizzy
  • 1,177
  • 2
  • 12
  • 34
  • 1
    Please post both your package.json files in their entirety. I am guessing that you may have stuff in devDependencies that you need for your Heroku build. That won't work by default. To see how to fix that, see https://stackoverflow.com/questions/48531545/heroku-failed-at-the-build-script-but-heroku-local-web-is-fine/48538277#48538277 and https://stackoverflow.com/questions/41973338/hosting-a-production-react-app-built-with-wepback-on-heroku?noredirect=1&lq=1. – Yoni Rabinovitch Feb 07 '18 at 07:16
  • Hey @YoniRabinovitch I've now edited this question to include the full package.json in the static folder. Let me know if you think it proves your hypothesis and I need to do some extra work for dev dependencies? See anything else out of the ordinary in there? I'm still unsure if the && npm start is necessary or if I should avoid that approach? – dizzy Feb 07 '18 at 18:44

1 Answers1

1

You should not have npm start in a postinstall script. You want to run your node.js server every time your web dyno restarts, not only every time your app is installed.

Furthermore, for Heroku, you should run the build of your "static" component in a heroku-postbuild script, not in a postinstall script.

Other than that, you need to make any build dependencies (such as webpack etc.) available to Heroku, either by setting config var NPM_CONFIG_PRODUCTION to false, or by moving them from "devDependencies" to "dependencies".

For more info see here.

Yoni Rabinovitch
  • 5,171
  • 1
  • 23
  • 34
  • what's the best way to run node.js server every time the dyno starts (have a recommendation for where to put that command, perhaps inside of gunicorn or something? Based on your experience does everything else look alright (server.js, etc?) – dizzy Feb 07 '18 at 23:47
  • I've now moved most of my devdepencies over to regular dependencies. However, I'm still a bit turned around on how to start my server every time the dyno starts (as well as running my server/flask side with gunicorn in procfile). As I continue to read more, I'm also really unsure on how to serve the /static/ (frontend). Is this something Heroku will just do natively once it sees package.json and a /dist/ folder? Grrr would really be helpful if they had some multi build pack documentation out there. – dizzy Feb 08 '18 at 06:42
  • for example, I see webpack -p in a lot of peoples "scripts" on the build step. Unsure what that does, but perhaps it's needed? – dizzy Feb 08 '18 at 06:48
  • 1
    Put a "start" script in your main package.json, and run your node.js server there. In addition, put a heroku-postbuild script in your main package.json, where you can build all your static assets. The "start" script will run on every dyno restart. The heroku-postbuild script will run only at slug compilation time. After that, all your static assets will be part of the slug, and available to all your dynos. – Yoni Rabinovitch Feb 08 '18 at 11:01
  • I'll give it a shot and report back! Thanks again Yoni. – dizzy Feb 08 '18 at 16:40
  • I've updated my root/package.json to: `{ "name": "rmmd", "version": "0.0.1", "engines": { "node": "6.11.1", "npm": "3.10.10" }, "scripts": { "start": "node static/bin/server.js", "heroku-postbuild": "cd static && npm install && npm run build:production" } } ` – dizzy Feb 09 '18 at 06:32
  • The build succeeded! I don't see issues 1 or 2 above in the logs. Matter of fact I see some promising stuff: [removed some text for space] heroku[router]: method=GET path="/dist/bundle.js" host=redacted.herokuapp.com dyno=web.1 protocol=https heroku[router]: method=GET path="/dist/bundle.css" host=redacted.herokuapp.com dyno=web.1 protocol=https heroku[web.1]: Starting process with command `gunicorn main:app` Starting gunicorn 19.6.0 Listening at: http://0.0.0.0:13521 (4) Using worker: sync However, front end is still not showing. Maybe I take this over to heroku boards? – dizzy Feb 09 '18 at 06:54
  • 1
    It sounds like your original questions have now been answered. You are now facing new issues. I suggest you open a new question with just the minimal possible code necessary to reproduce your new issue, and no extraneous info. – Yoni Rabinovitch Feb 09 '18 at 14:05
  • Hey Yoni, I wanted to refer back to this question even though it's closed. I've just realized that Heroku doesn't call npm start automatically if something else is specified in the procfile. In my case, it's already calling flask. Do you have any thoughts on where to call NPM start then? – dizzy Feb 13 '18 at 05:40
  • 1
    Try switching the order of your buildpacks, so that the node.js buildpack is at index 1. See https://devcenter.heroku.com/articles/using-multiple-buildpacks-for-an-app. I am not 100% sure that will help, but worth a shot. – Yoni Rabinovitch Feb 13 '18 at 09:59
  • 1
    Wait, I'm confused. What are you actually trying to achieve? You have a flask web server, and a react client. Your procfile runs your flask web server. What do you need a node.js server for? – Yoni Rabinovitch Feb 13 '18 at 10:30
  • I'll give the buildpack switch a shot. The problem I'm having is that the frontend still isn't displaying on my app. I felt like it was because server.js isn't running like I do on my local? Figured I'd give it a shot. Generally on local I runserver for my python application and then npm start my react application. Both run on different ports. – dizzy Feb 13 '18 at 15:43
  • Tried the buildpack swap. Doesn't appear to be executing server.js. I'll explore some different options. Thanks for such great responsiveness Yoni! – dizzy Feb 14 '18 at 01:17
  • Just wondering if you figured this out? Are you trying to deploy 2 different web servers in a single heroku app? That won't work. Why do you need both a flask web server and a node.js web server? Can you simply serve your static assets directly from your flask web server, and get rid of the node.js web server? – Yoni Rabinovitch Feb 19 '18 at 09:59