Read this before using Create React App with Rails
If you are reading this, you probably appreciate that Rails lets you follow some conventions and everything just works. Instead of spending time configuring development tools, you prefer to add cool features to your app. This means you probably thought Create React App was a no-brainer as soon as you heard about it. No configuring webpack or any build process anything, just use the CLI and start writing React code. Before you set off on a multi-hour journey of getting these two to work together, read this.
There are several well-documented ways of implementing React on Rails.
Unfortunately most of them include some kind of Gem like Webpacker React on Rails or React Rails
Gems are great. But if you build something sufficiently complex in your front-end React code, sooner or later, you will ask someone for help. It would make sense to pick a specialist that spend all their time in front-end Javascript and React code. What do you think they are going to say when they try to take a quick look at your build process config and they realize there are Ruby gems involved in the process? Okay, maybe they are nice like the JS developers I know and they try lovingly start reading the documentation for the Gem to figure out what it is doing. But I bet you they would be thinking “Why the hell is a Ruby library involved in building this React app? And why is the Asset Pipeline involved?”. This should have nothing to-do with Ruby or Rails.
Another approach is to just handle the React and Rails apps as completely
separate applications. In the root of your project, do a rails create server
and create-react-app client
then you can either host them separately with
Rails only being an api. In development you would start two servers, visit the
CreateReactApp on it’s port and configure it to communicate with the Rails api
back-end. In production you could host them separately or build a Docker image
that has the yarn build
output from the React app in the /public
folder of
the Rails app and the Rails app serve it from there.
This approach works. I really like the simplicity because you are on the happy-path of following all the conventions for both ecosystems.
Unfortunately the app I am building needs to have the back-end handling Authentication. We want the back-end to authenticate you if you are not signed in and hand your browser a session-cookie. This process includes redirecting the user’s browser to a 3rd party SSO provider. The above approach doesn’t work for this, because in development, your browser is hitting the Webpack Dev Server directly and Rails never gets a direct browser request I guess I could have tried to dig deeper here to see if I can get the redirect to happen or we could have considered changing the requirements and implemented all kinds of security in the front-end. But remember, this whole journey started with a desire for “Happy path, minimal configuration, Get to writing code”. So I moved on.
Here is what I spent several hours trying to get working before I quit:
rails new rails-react --skip-sprockets #this skips the asset pipeline
cd rails-react
git add .
git commit -m 'initial rails'
create-react-app client
#a lot more steps here, get in touch if you want to take this further
Where this is going is a Create React App application that is self contained but Rails serves it to you.
I thought it was brilliant, and I was probably correct.
Rails would start up, I would tell it to serve static assets, I would tell it that the static assets are hosted where the Webpack Dev Server is running the create-react-app code. And it would load.
The poop hit the fan right at hot reloading. The Webpack Dev Server setup has the browser open a web socket to the dev server listening for a change, and if there is a change, the browser would reload the whole page. You can view this in your Network tab of Chrome dev tools. The problem was that while Rails was serving you the JS, and I could configure Rails to go get it from the WDS, but the JS itself tried to open the socket, and it would request to open that socket to the Rails server on port 3000 instead of the WDS which I had running on port 3001. This is not configurable with the current version of CRA. I looked into several ways of messing with it, but ultimately decided against it.
In the end, I went with a minimal webpack.config.json
in the root of the app
that starts a WDS and serves a React app I am building in /client
. The
reason this does work is because the WDS can be configured with a
PUBLIC_PATH
which the JS code then uses to hit the WDS instead of just
using the server they were served from (which is Rails).
You will need the following in your development.rb
and production.rb
:
# This lets Rails serve the Javascript as if it is a static asset sitting on a CDN somewhere
config.public_file_server.enabled = true
You will need a <%= javascript_include_tag(asset_url_for('js')) %>
in your
app/views/layouts/application.html.erb
This means your browser will load the JS when you try to load the Rails app. This asset URL will probably be different between Development and Production.
And lastly a normal Rails view that only contains <div id="root"></div>
and
then the React app uses that as a target to load itself into.
I hope this saves you some of the time I spent. At least if you do decide to
still have Rails serve a CRA app, you can go straight to figuring out how to
configure it’s front-end code with a custom PUBLIC_URL
. Feel free to get in
touch if you have any questions.