Note: This Rails version targeted in the post is one before webpacker got added. The folder structure and procedure might differ if you're using a newer version of Rails. But the context does help.
Deployment of a Rails app that uses Webpack to Heroku is tricky because it requires both Node.js and Ruby. After various unsuccessful attempts, I had a solution that worked without customizing Heroku buildpacks. If you know how Heroku buildpacks work, skip to the summary for the TL;DR version.
It's worth pointing out that there's already a well supported React gem
for use with Rails
react-rails, but we chose to use npm
instead. We wanted to leverage the latest version(s) of React, Redux,
ESLint, and we felt that using the npm-based package management workflow
works best in this case.
This is the application directory structure we decided to go with:
... (usual rails directories)
package.json required by npm was placed inside the
webpack/ directory, but this made deployment to Heroku a bit
complicated. The Webpack asset compilation step bundles all the
react_bundle.js and places
it under the
loaded into Rails' asset pipeline. So the prerequisite for the asset
pipeline to work properly is that a
react_bundle.js file must
be present in
step is run during deployment.
Heroku uses setup scripts they call
figuring out the type of application, and to run the necessary build
steps. For example, Heroku runs build steps for a Rack-based application
if it finds a
Gemfile in the root directory. If it finds a Railties
gem in the Gemfile specs, it will trigger the Rails related build steps.
At a simplified level, these are the steps that are run for a Rack
- Check for
Gemfile.lockin the root directory. If it's present, assume this is a Ruby application and start Ruby related build steps.
- Check to see if specs in
Gemfile.lockhas Rack gem as a dependency.
- Install necessary Ruby version
- Install bundler and install all dependencies
- Run a web process based on a Procfile or the default web process
In the same way, Heroku figures out the build steps for a Node.js
application based on the presence of a
package.json file in the root
directory. So what if we need both npm related steps and Ruby related
steps? We need to keep both the
package.json in the root
directory. That's step 1.
Multiple Heroku Buildpacks
Heroku supports having multiple buildpacks for a
single application. If we use both Node.js and Ruby buildpacks, we need
a way to tell Heroku to run the npm-related steps first, and only then
to run the Ruby related steps. This is to ensure that the
react_bundle.js file is present under
Rails' asset pipeline commands are run. The Heroku command line provides
a way configure the build packs and specify an order to them:
heroku buildpacks:set heroku/nodejs
heroku buildpacks:add heroku/ruby --index 2
--index argument means the
heroku/ruby buildpack is inserted
heroku/nodejs buildpack. This completes step 2.
Post build hook on Heroku
Once the buildpacks are setup, we need a way to run a Webpack command
right after the Node.js setup is completed, and just before the
Ruby/Rails related setup starts. Heroku's Node.js buildpack provides a
way for users to run a custom script right after the normal
Node.js build steps are completed. For this, a command must be specified
package.json under the
scripts section with a
key. Here is the relevant section from
"webpack:deploy": "webpack --config=webpack/webpack.config.js -p",
"heroku-postbuild": "npm run webpack:deploy"
This completes step 3. With these three steps, I was able to setup a workable Webpack + Rails scaffold on Heroku.
- Put the
package.jsonin the root folder of the Rails application.
- Setup multiple buildpacks on Heroku via Heroku's command line utility,
heroku/nodejsin the first position and
- Add the webpack bundling command in
heroku-postbuildas the key.