Expo Router “brings the best routing concepts from the web to native iOS and Android apps” and is a great solution when building React Native apps for web too.
Getting it to run on Heroku (together with a Ruby on Rails api-only application), is not really straigforward as of now though, so here is a small tutorial:
Have your Expo application inside the Rails directory structure
We chose app/frontend
, but you can put it into other locations too.
Use the right Heroku buildpacks
Make sure that heroku/ruby
and heroku/nodejs
buildpacks are used for your project.
Set up a build script for your Expo project
Your Expo project needs to be built into a bunch of static files on each deploy automatically and placed inside a folder of your choice (we use /expo-spa
):
{
"build": "cd app/frontend && yarn && yarn expo export --platform web --output-dir ../../public/expo_build/"
}
This will output the following folders inside /public
on each deploy:
public/
└── expo_build/
├── assets/
│ ├── node_modules/
│ ├── some-hash-1
│ ├── …
│ └── some-hash-n
├── bundles/
│ └── web-some-hash.js
└── index.html
Serve the Expo build static files via Rails
To serve your build artifacts as static files via Rails, first create a controller expo_build_controller.rb
. It has to include ActionController::MimeResponds
manually, if you are using Rails in API only mode:
class ExpoBuildController < ApplicationController
include ActionController::MimeResponds
def index
respond_to do |format|
format.html { render body: Rails.root.join('expo-spa/index.html').read }
end
end
def bundles
respond_to do |format|
format.js { render body: Rails.root.join("expo-spa/bundles/#{request.params[:path]}.js").read }
end
end
def assets
render body: Rails.root.join("expo-spa/assets/#{request.params[:path]}.#{request.params[:format]}").read
end
end
Now you have to route requests to the static files Heroku will build when deploying:
# Serve Expo React Native Web SPA
get '*path', to: 'expo_build#index', constraints: lambda { |request|
!request.xhr? && request.format.html? # Prevents API calls from being redirected to the SPA
}
get '/bundles/*path', to: 'expo_build#bundles'
get '/assets/*path', to: 'expo_build#assets'