mars/create-react-app-buildpack

Using in conjunction with https://github.com/lstoll/heroku-buildpack-monorepo causes create-react-app-buildpack to leak resources in heroku build cache ...

breathe opened this issue ยท 4 comments

I have a create-react-app app in a subdirectory of another project which is also deployed via heroku ...

Heroku's mono-repo support isn't great -- but I'm using the https://github.com/lstoll/heroku-buildpack-monorepo buildpack to independently deploy a create-react-app app from a sub-directory with APP_BASE referring to the root directory of my create-react-app project.

I'm seeing the size of my dyno images grow without bounds -- and eventually have to clear the heroku cache.

It appears the contents of node_modules/.cache are growing without bound across builds ...

Here's a heatmap of files in my create-react-app slug

image

Everything to left of the white line are files within

  • node_modules/.cache/babel-loader
  • node_modules/.cache/eslint-loader
  • node_modules/.cache/terser-webpack-load
mars commented

Hello @breathe ๐Ÿ˜„

If we have a node_modules/ problem, then the Node buildpack would be the responsible party. This create-react-app-buildpack does not do the npm magic itself.

The observed unbound growth is probably a side-effect of Node buildpack changes made to cache and improve build time over the years. At conflict with that is heroku-buildpack-monorepo manipulating the build paths in unofficial and (at this point) untested ways.

The best solution I have for you is break up your monorepo, so that each Heroku app has its own repo. That's really the only supported way to use Heroku.

I personally use monorepos via Terraform configuration, which allows combining multiple apps and other cloud services together within a single repo. Terraform is pretty sweet, but it's not officially supported either, so will find different issues with it!

Preventing the node buildpack from caching node_modules is good enough for now for me ...

heroku config:set NODE_MODULES_CACHE=false --app appname

Hi @breathe
I'm having difficulties with getting the create-react-app-buildpack and heroku-buildpack-monorepo work together. In which order you have the buildpacks? Or is there something else I should take into consideration to be able to run my cra-app on heroku?

My dir structure (important parts in parenthesis) is like this:

  • root/package.json ( scripts: { "heroku-postbuild": "./heroku.build" } )
  • root/heroku.build ( if BUILD_ENV is something -> cd client && npm start )
  • root/client
  • root/api
  • root/api/Procfile (web: node build/index.js)
  • root/client/Procfile (web: npm start)

The api (backend) part runs smoothly, so I guess it is something related to the create-react-app. So since you have been able to run the stack successfully, could you provide a tip how to gain that? :D

Config variable is also set as required like this:
APP_BASE=client

And here's my buildpacks:
image

Sharing my experience to deploy a CRA as a package inside a monorepo (yarn workspaces)
I managed to get it working smoothly as following:

Summary first:

  • Add your express server
  • build your CRA
  • Procfile
  • use necessary buildpacks

Express server

create a very simple express server to serve your CRA app, as following

const path = require('path');
const express = require('express');

const app = express();
const port = Number(process.env.PORT) || 3000; // read process.env.PORT from heroku environment

app.use(express.static(path.join(__dirname, 'build'))); // here we serve all the statics

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

app.listen(port, () => {
  console.log(`Up and  running on http://localhost:${port}`);
});

make sure to update (__dirname, 'build', 'index.html') and path.join(__dirname, 'build') based on your server location, i use server.js right on the root of my CRA for simplicity.

build your CRA

Heroku by default will look for package.json->scripts->build but only at the root package.json
In order to make this work, you need to pass that through the root package.json

// root package.json

"scripts": {
  "build": "yarn workspaces run build", // this will trigger all "build" scripts for all packages into your monorepo (more on that later)
}

// your CRA package.json

{
  "build": "react-scripts build"
}

Procfile

add Procfile into the root of your package (example, /packages/mywebapp/Procfile)

use heroku-buildpack-multi-procfile
under your heroku app settings create env var PROCFILE=path/to/your/app/Procfile

Here's my Procfile

web: node packages/web/server.js

use necessary buildpacks

I use only two buildpacks:

  • heroku/nodejs
  • heroku-buildpack-multi-procfile

Make sure only needed builds run while deploying

let's say you have 2 or more apps, and you only want to run package.json scripts.build for the only one you are deploying:

you could do the following:

  1. define env var WEB_APP_ENV=true into your heroku app

  2. use if-env and npm-run-all on your root package.json

{
  "build": "run-p build:*",
  "build:web": "if-env WEB_APP_ENV=true && yarn workspace my-web-app run build || echo 'skip build:web'",
}

this way, only one application will be deployed based on env variables.

Summary

here's a summary of folder/file structure:

package.json (root package.json)
packages
   web
      package.json
      server.js
      src
      Procfile
      build (generated on CRA build)
   api
     Procfile
     server.js
     ...

and here're some screenshots that could help:
image
image

hope it's not complex, and simple to follow! it took me around 1 hour to deploy my CRA! ๐Ÿš€