railsAPIモードとReactで記事投稿アプリを作りました。API側とクライアント側を別々にHerokuにデプロイすると、SPAとは思えないほどアプリの動きが重くなったので、1つのdynoでデプロイし直した時のことをメモします。
##参考になった記事
A Rock Solid, Modern Web Stack—Rails 5 API + ActiveAdmin + Create React App on Heroku | Heroku
ReactJS + Ruby on Rails API + Heroku App - Medium
React + RailsのアプリをHerokuで動かす方法 - Qiita
##はじめに
###ディレクトリ構成
project
┝ app
┝ bin
┝ config
┝ db
┝ front <--reactのディレクトリ
┝ lib
┝ log
┝ public
┝ storage
┝ test
┝ tmp
┗ vendor
アプリ名をprojectとしています
ディレクトリ構成はrailsアプリの中にreactアプリがあるという状態です
アプリのルートディレクトリにGemfileがないと後々buildpackを用いてデプロイする時にエラーが出るのでこのような構成にしました
参考:Heroku:Buildpackエラーでハマった
##.envファイル生成
開発環境でのAPI側のURLを.envファイルに書きます
frontディレクトリ下で.envファイルを作成し、以下を記述します
REACT_APP_SERVER_URL=http://localhost:3001
URLを取得する時はprocess.env.REACT_APP_SERVER_URL
で取得できます
axiosでHTTP通信する場合は以下のように書けます
axios
.get(`${process.env.REACT_APP_SERVER_URL}/api/users`, headers)
##Foremanの導入
Foremanは複数のプロセスをまとめて管理できるツールです
APIとクライアント側を一つのコマンドで動かせるので便利です
Gemfileに以下を記述し、bundle install --path vendor/bundle
します
gem 'foreman'
次にProcfile.devを作成し、以下を記述します
frontend: exec /usr/bin/env PORT=3000 sh -c 'cd front && yarn start'
backend: exec /usr/bin/env PORT=3001 sh -c 'bundle exec rails s'
ここでforeman start -f Procfile.dev
を実行し、http://localhost:3000
にアクセスすると確認できます
ただ、lib/tasks
下にstart.rakeファイルを作成し、以下を記述すると、rake start
とコマンドに打つだけでブラウザで確認できます
namespace :start do
desc 'Start dev server'
task :development do
exec 'foreman start -f Procfile.dev'
end
desc 'Start production server'
task :production do
exec 'NPM_CONFIG_PRODUCTION=true yarn heroku-postbuild && foreman start'
end
end
task :start => 'start:development'
また、rake start:production
コマンドでデプロイ前にローカルで簡単に本番環境のテストを行うことができるように記述しています
##Herokuにデプロイ
###package.json作成
まず、projectディレクトリ下にpackage.jsonファイルをnpm init
コマンドで作成します
{
"name": "project",
"version": "1.0.0",
"description": "You can post articles using this application.",
"main": "index.js",
"scripts": {
"build": "cd front && npm install && npm run build && cd ..",
"deploy": "cp -a front/build/. public/",
"heroku-postbuild": "npm run build && npm run deploy && echo 'Client built!'"
}
}
Herokuはheroku-postbuild
に定義されていることを実行します。ここでは、frontディレクトリでnpm install, npm run build
を実行し、front/buildの内容をpublic/にコピーします
###Herokuアプリ作成
heroku create アプリ名
でHerokuアプリを生成します
そして、Herokuのbuildpackを追加します
$ heroku buildpacks:add heroku/nodejs --index 1
$ heroku buildpacks:add heroku/ruby --index 2
heroku buildpacks
で確認して、以下のような順番になっていればOKです
=== project Buildpack URLs
1. heroku/nodejs
2. heroku/ruby
###Procfile作成
次にProcfileを作成し、Herokuがrailsアプリを起動するためのコマンドを記述します
web: bundle exec rails s
Procfileを作成したため、ここでrake start:production
コマンドによりローカルで本番環境のテストを行うことができます
###本番環境用のエンドポイント設定
Reactアプリが本番環境でのAPI側のURLを参照できるように.env.productionファイルを作成します
REACT_APP_SERVER_URL=https://project.herokuapp.com
また、クライアント側からのアクセスを許可するため、cors.rbのoriginに追記します
origins 'http://localhost:3000', 'https://project.herokuapp.com/api'
本番環境では、APIはhttps://project.herokuapp.com/api/
で、クライアント側はhttps://project.herokuapp.com/
で起動するようになっています
###.gitignoreに追記
次に.gitignoreに以下を追記します
/public
/vendor/bundle
###Postgre.SQLを使用するための設定
本番環境のDBにPostgre.SQLを使用するための設定を行います
Gemfileに以下を追記し、bundle install --without production
を実行します
group :production do
gem 'pg', '>= 0.18', '< 2.0'
end
次にdatabase.ymlを以下のように書き換えます
production:
adapter: postgresql
encoding: unicode
pool: 5
database: project_production
username: project
password: <%= ENV['PROJECT_DATABASE_PASSWORD'] %>
###デプロイ
これでデプロイする準備が整いました
Herokuにpushしてください
git add .
git commit -m 'ready for first push to heroku'
git push heroku master
heroku run rails db:migrate
して終了です
##React Routerを使っている場合
React Routerを機能させるために以下の設定をします
###fallback_index_htmlメソッド定義
ApplicationControllerにfallback_index_htmlメソッドを追加します
class ApplicationController < ActionController::Base
def fallback_index_html
render :file => 'public/index.html'
end
end
ApplicationControllerがActionController::APIを継承している場合はhtmlファイルを返すことができないため、以下のように追記します
class ApplicationController < ActionController::API
include ActionController::MimeResponds
def fallback_index_html
respond_to do |format|
format.html { render body: Rails.root.join('public/index.html').read }
end
end
end
###route.rbでfallback_index_html
そして、routes.rbにも以下を追記します
railsのルーティングについて書いている部分より下に追記してください
ここでfallback_index_htmlメソッドによりpublic/index.htmlが返されるため、React Routerを参照することができます
Rails.application.routes.draw do
get '*path', to: "application#fallback_index_html", constraints: ->(request) do
!request.xhr? && request.format.html?
end
end
これでpushすると、React Routerによるルーティングが反映されると思います
Herokuにデプロイするまでの手順は以上です
##最後に
参考にしたサイトを見ながらでも結構エラーで詰まるところがあり、デプロイするまでに時間がかかりました。しかし、APIとクライアントを別々でデプロイするよりもアプリの動きは軽くなったのでよかったです。
この記事について間違っているとこなどあればご指摘いただけると嬉しいです。