Rails+Reactアプリを1つのdynoでデプロイする

railsAPIモードとReactで記事投稿アプリを作りました。API側とクライアント側を別々にHerokuにデプロイすると、SPAとは思えないほどアプリの動きが重くなったので、1つのdynoでデプロイし直した時のことをメモします。

##参考になった記事
[A Rock Solid, Modern Web Stack—Rails 5 API + ActiveAdmin + Create React App on Heroku | Heroku](https://blog.heroku.com/a-rock-solid-modern-web-stack)
[ReactJS + Ruby on Rails API + Heroku App - Medium](https://medium.com/@bruno_boehm/reactjs-ruby-on-rails-api-heroku-app-2645c93f0814)
[React + RailsのアプリをHerokuで動かす方法 - Qiita](https://qiita.com/pure-adachi/items/c2c5730560650c80a5e0)

##はじめに
###ディレクトリ構成
```
project
      ┝ app
      ┝ bin
      ┝ config
      ┝ db
      ┝ front <--reactのディレクトリ
　　　 ┝ lib
      ┝ log
      ┝ public
      ┝ storage
      ┝ test
      ┝ tmp
      ┗ vendor
```
アプリ名をprojectとしています
ディレクトリ構成はrailsアプリの中にreactアプリがあるという状態です
アプリのルートディレクトリにGemfileがないと後々buildpackを用いてデプロイする時にエラーが出るのでこのような構成にしました
参考：[Heroku:Buildpackエラーでハマった](https://qiita.com/arekore0/items/eae550bdbcc78eeac082)

##.envファイル生成
開発環境でのAPI側のURLを.envファイルに書きます
frontディレクトリ下で.envファイルを作成し、以下を記述します

```text:project/front/.env
REACT_APP_SERVER_URL=http://localhost:3001
```
URLを取得する時は```process.env.REACT_APP_SERVER_URL```で取得できます
axiosでHTTP通信する場合は以下のように書けます

```jsx:project/front/Component/UserIndex.js
axios
      .get(`${process.env.REACT_APP_SERVER_URL}/api/users`, headers)
```

##Foremanの導入
Foremanは複数のプロセスをまとめて管理できるツールです
APIとクライアント側を一つのコマンドで動かせるので便利です
Gemfileに以下を記述し、```bundle install --path vendor/bundle```します

```ruby:project/Gemfile
gem 'foreman'
```

次にProcfile.devを作成し、以下を記述します

```text:project/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```とコマンドに打つだけでブラウザで確認できます

```ruby:project/lib/tasks/start.rake
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```コマンドで作成します

```jsx:project/package.json
{
  "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アプリを起動するためのコマンドを記述します

```text:project/Procfile
web: bundle exec rails s
```
Procfileを作成したため、ここで```rake start:production```コマンドによりローカルで本番環境のテストを行うことができます

###本番環境用のエンドポイント設定

Reactアプリが本番環境でのAPI側のURLを参照できるように.env.productionファイルを作成します

```text:project/front/.env.production
REACT_APP_SERVER_URL=https://project.herokuapp.com
```
また、クライアント側からのアクセスを許可するため、cors.rbのoriginに追記します

```ruby:project/config/initializers/cors.rb
origins 'http://localhost:3000', 'https://project.herokuapp.com/api'
```
本番環境では、APIは```https://project.herokuapp.com/api/```で、クライアント側は```https://project.herokuapp.com/```で起動するようになっています

###.gitignoreに追記

次に.gitignoreに以下を追記します

```text:project/.gitignore
/public
/vendor/bundle
```
###Postgre.SQLを使用するための設定
本番環境のDBにPostgre.SQLを使用するための設定を行います
Gemfileに以下を追記し、```bundle install --without production```を実行します

```ruby:project/Gemfile
group :production do
  gem 'pg', '>= 0.18', '< 2.0'
end
```
次にdatabase.ymlを以下のように書き換えます

```ruby:project/config/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メソッドを追加します

```ruby:project/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  def fallback_index_html
    render :file => 'public/index.html'
  end
end
```
ApplicationControllerがActionController::APIを継承している場合はhtmlファイルを返すことができないため、以下のように追記します

```ruby:project/app/controllers/application_controller.rb
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を参照することができます

```ruby:project/config/routes.rb
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とクライアントを別々でデプロイするよりもアプリの動きは軽くなったのでよかったです。
この記事について間違っているとこなどあればご指摘いただけると嬉しいです。