Monorepo + Yarn + Deploying multiple apps on Heroku = Lots of fun

  • react
  • coding
  • heroku

So you organized the code of your application in a monorepo, as all the cool kids do nowadays. You probably have one or multiple single page applications, some backend API and shared libraries there.

You may be using yarn workspaces with all the apps and libraries gathered in directories under packages/*.

Now you want to deploy all of that to heroku. The first natural problem: Heroku usually operates under a one application per repository premise.

The first solution for deploying multiple apps from a single repository are custom buildpacks like heroku-buildpack-multi-procfile or heroku-buildpack-monorepo.

They all work very similar: they allow you to store multiple apps in sub-directories in your git repo. On build, they either copy the Procfile or the full sub-directory to the root. Which file or sub-directory to choose is provided via an environment variable.
After this copying, Heroku would pick up the Procfile or start/build scripts from package.json - as with every other application.

Easy enough.

But: this approach has issues with actual monorepos.

  1. Copying everything under packages/something to the root of the folder might break configuration files for typescript, eslint, prettier etc. already existing there - and it would overwrite package.json in the root.
  2. Depending on the hoisting behaviour of node_modules, just copying a yarn workspace into a different directory can break your dependency resolution.
  3. The same might happen for dependencies between workspaces. E.g. packages/package-a depending on packages/package-b and being linked together.

There's an easier way

Imagine your repository has an api, frontend and shared library:

/ <root>
- package.json
- /packages
  - /api
    - package.json
  - /frontend
    - package.json
  - /shared
    - package.json

With this layout, you can use the scripts section of the root package.json to run commands in the individual workspaces:

{
"name": "workspace-root",
"scripts": {
"start": "yarn workspace api run start"
},
"workspaces": ["packages/*"]
}

This would run the start script in packages/api/package.json (assuming the name in that file is set to api).

These scripts interpolate environment variable, so we can set one which tells us, what workspace to run the command in:

  "scripts": {
"start": "yarn workspace $WORKSPACE_NAME run start"
},

Now you just create two apps from the same repo using different values for that environment variable:

# Create the api app using the api workspace
heroku create api --remote heroku-api
heroku -a api config:set WORKSPACE_NAME api
# Create the frontend app using the frontend workspace
heroku create frontend --remote heroku-frontend
heroku -a frontend config:set WORKSPACE_NAME frontend

That's all. Now you're running multiple apps from the same repository without even using a special buildpack or messing around with your folder layout.

There's more

The same works, of course, for all the other scripts (build, heroku-postbuild etc) Heroku uses during the build/release cycle.

You can also use it for scripts like release which are not available via package.json scripts only. Just create a Procfile which delegates the work to package.json:

Procfile

release: yarn release

package.json

  "scripts": {
"start": "yarn workspace $WORKSPACE_NAME run start",
"release": "yarn workspace $WORKSPACE_NAME run release"
},

Remember: keep things simple ;)