Serving multiple React apps from a single domain

In my playing around with React and Redux/Relay I wanted a way to serve them up from one host. Along with the Rails GraphQL/API backend.

There were plenty of examples of getting a single React app up and running with rewrite rules or .htaccess. But I wanted things a little differently. Mostly I didn’t want to have to have the trailing /.

I had initially set this up with Apache 2.4 and it was working great, until I wanted to deploy it with ActionCable integration I spent way too much time trying to get websockets to work with Apache so I ended up switching to nginx. So long Apache, ~20 years was a good run.

Here’s the Apache config I was using.

<VirtualHost :443>
  ServerName example.com
  RailsEnv production
  RewriteEngine On
  DocumentRoot /path/to/your_app/public

  <Directory /path/to/your_app/public>
    Require all granted
  </Directory>

  <LocationMatch "/redux.*">
    # match without a trailing /
    RewriteCond %{REQUEST_URI} ^/redux$
    RewriteRule ^ %{DOCUMENT_ROOT}/redux/index.html [L]

    # handle any files local to the parent directory
    # this allows deep links into the app to work
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
    RewriteRule ^ - [L]
    RewriteRule ^ %{DOCUMENT_ROOT}/redux/index.html [L]
  </LocationMatch>

  <LocationMatch "/relay.*">
    RewriteCond %{REQUEST_URI} ^/relay$
    RewriteRule ^ %{DOCUMENT_ROOT}/relay/index.html [L]

    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
    RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
    RewriteRule ^ - [L]
    RewriteRule ^ %{DOCUMENT_ROOT}/relay/index.html [L]
  </LocationMatch>
</VirtualHost>

And here’s the same config for nginx

server {
  listen 443 ssl;
  server_name example.com;
  passenger_enabled on;
  passenger_app_env production;
  root /path/to/your_app/public;

  # match without a trailing /
  location ~ ^/redux$ {
    try_files $uri/index.html /index.html;
  }

  # handle any files local to the parent directory
  # this allows deep links into the app to work
  location /redux {
    try_files $uri $uri/ $uri/index.html /redux/index.html;
  }

  location ~ ^/relay$ {
    try_files $uri/index.html /index.html;
  }

  location /relay {
    try_files $uri $uri/ $uri/index.html /redux/index.html;
  }
}

By the way, that nginx config is the actual config that is serving up the Rails app for API and GraphQL, React Apps and the ActionCable endpoint.


Integration testing a Rails/React app with RSpec

I’ve been playing around with React lately.

I built a Rails API backed app as a learning exercise, the app is a Todo List, but not the usual TodoMVC. Think multiple users with multiple lists of items. I felt that would be enough exposure into the React way of doing things.

Well, actually since I like to torture myself when learning new things, I started out doing this in Relay, including building the GraphQL server using graphql-ruby.

Next stop was doing it in Vanilla React, which felt horribly painful after all the magick that Relay brings to the table.

After that came doing it again with Redux, I figured it made sense to expose myself to more than one way of doing things in React.

Once I got it all running halfway decently I wanted to try and get RSpec integration tests up and running. After much searching and finding random tidbits that worked here and there, once I got a working setup I figured I’d document it. This setup will save a screenshot on failure and open it in the appropriate app (I’ve only tested this bit on macOS).

You will need to install chromedriver, you can get it here or install it via homebrew.

Here are the relevant files for a working (as of 2018-02-26) RSpec/React integration test setup.

Gemfile

...

group :test do
  gem 'capybara', '~> 2.4.4'
  gem 'capybara-screenshot', '~> 1.0.11'
  gem 'database_cleaner'
  gem 'rspec-rails'
  gem 'selenium-webdriver'
end

Procfile-react-test

web: cd clients/react && yarn start
api: RAILS_ENV=test bundle exec rails s -p 3001

spec/rails_helper.rb (add this bit after require 'rspec/rails')

Dir['spec/support/**/*.rb'].each do |file|
  require Rails.root.join(file).to_s
end

spec/support/capybara.rb

require 'capybara/rspec'
require 'capybara/rails'
require 'capybara-screenshot/rspec'

Capybara.app_host = "http://localhost:3000"
Capybara.default_wait_time = 5

Capybara::Screenshot.webkit_options = {
  width: 1024,
  height: 768
}

Capybara::Screenshot.autosave_on_failure = false
Capybara::Screenshot.prune_strategy = :keep_last_run

RSpec.configure do |config|
  config.after do |example|
    if example.metadata[:type] == :feature and example.metadata[:js] and example.exception.present?
      Capybara::Screenshot.screenshot_and_open_image
    end
  end
end

spec/support/capybara_drivers/selenium.rb (I have multiple files as I was testing different drivers, that’s also the reason for catching LoadError)

begin
  require 'selenium/webdriver'
  require 'capybara/rspec'

  Capybara.register_driver :selenium do |app|
    options = Selenium::WebDriver::Chrome::Options.new(
      args: %w[headless disable-gpu no-sandbox]
    )
    Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
  end

  Capybara.javascript_driver = :selenium
rescue LoadError
end

spec/features/signups_spec.rb

require 'rails_helper'

RSpec.feature "Signups", type: :feature, js: true do

  scenario "Visitor signs up for a new account" do
    visit("/signup")
    fill_in "name", with: "Bob"
    fill_in "email", with: "bob@example.com"
    fill_in "password", with: "password"
    click_button "Create Account"
    expect(page).to have_text("Logout Bob")
  end

end

To run the tests you need to start the server and client.

foreman start -p 3000 -f Procfile-react-test

And run your feature specs

% bundle exec rspec spec/features
.

Finished in 4.98 seconds (files took 9.46 seconds to load)
1 example, 0 failures

And there you have it, those are the changes you need to get this working.


JABE 0.8.0

What’s new:


JABE 0.6.1, Rails 3.1+ and mountable

So I finally got around to updating JABE yeah I know its been forever (about a year).

Well it now plays nice with Rails 3.1+ and you can now mount it to any path you want instead of just the root.


Sass on Heroku or any generated files for that matter

Here’s yet another take on how to handle generated files when you are deploying to “Heroku”:http://www.heroku.com/ while keeping your git repo free of the artifacts.

We deploy using a rake task rake deploy that was generating Jammit files, committing them to the repo and pushing them up. This works fine, but adds “junk” commits to your repo and even the occasional merge conflict from the generated files.

Since Heroku doesn’t care what you push to it as long as it is in the master branch on their end, why not generate the assets in a throwaway branch and push that up?

Here are the basic commands you would run in the shell. Wrap them up in a rake task that fits your project and off you go.

# send it up to github
git push origin master

# get rid of the branch if it exists (it shouldn't)
git branch -D deploy

# generate Jammit, Sass, etc. files
rake cache_assets

# push it up to heroku, need force since it's a different branch every time
git push heroku deploy:master --force

git checkout master

# get rid of the old branch
git branch -D deploy

As long as everybody uses the rake task to deploy you shouldn’t have any problems with this technique.