Next.js + Rails プロジェクトのセットアップ手順
フロントエンド Next.js + バックエンド Rails のセットアップ手順を整理します。
1. リポジトリの作成
GitHub 上に新規リポジトリを作成し、ローカルにクローンします。
git clone [GITHUB_URL]
プロジェクト名は project
とします。
cd project
2. Next.js の作成
project/project-frontend
として Next.js アプリを作成します。
- Node 15.8.0
npx create-next-app project-frontend
yarn
を実行します。
cd project-frontend
yarn
3. Rails の作成
project/project-backend
として Rails アプリを作成します。
- Ruby 3.0.0
- Rails 6.1.2
rails new project-backend --skip-spring --api
4. Git の調整
自動生成される Next.js, Rails の .git を削除します。
cd project-frontend
rm -rf .git
cd project-backend
rm -rf .git
.gitignore
の統合
ルート層に .gitignore
を作成し、Next.js, Rails の .gitignore
内容を統合します。(それぞれの内容に /project-frontend/
もしくは /project-backend
を付け加えます。)
統合後、Next.js, Rails の .gitignore
を削除します。
5. 開発環境の Docker 化
開発環境を Docker 化していきます。
Next.js の Docker 化
project-frontend
の設定です。
Dockerfile
Next.js 用の Dockerfile
を追加します。
FROM node:15.8
ENV APP /usr/src/app
RUN mkdir $APP
WORKDIR $APP
COPY package.json yarn.lock $APP/
RUN yarn
COPY . $APP/
CMD ["yarn", "dev"]
Rails の Docker 化
project-backend
の設定です。
MySQL の設定
DB は MySQL を使うため、Gemfile
を更新します。
# Use sqlite3 as the database for Active Record
# gem 'sqlite3', '~> 1.4'
gem 'mysql2'
続いて、database.yml
の設定を次のように更新します。
default: &default
adapter: mysql2
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
charset: utf8mb4
encoding: utf8mb4
collation: utf8mb4_general_ci
local: &local
socket: /var/run/mysqld/mysqlx.sock
port: <%= ENV['DB_PORT'] %>
host: <%= ENV['DB_HOST'] %>
username: <%= ENV['DB_USERNAME'] %>
password: <%= ENV['DB_PASSWORD'] %>
development:
<<: *default
<<: *local
database: project_development
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
<<: *default
<<: *local
database: project_test
production:
<<: *default
url: <%= ENV['DATABASE_URL'] %>
socket: /tmp/mysql.sock
起動スクリプト
開発サーバー用の起動スクリプトを bin/server-dev
に追加します。
#!/bin/bash -i
# Wait for MySQL
until nc -z -v -w30 $DB_HOST $DB_PORT; do
echo 'Waiting for MySQL...'
sleep 1
done
echo "MySQL is up and running!"
# If the database exists, migrate. Otherwise setup (create and migrate)
bundle exec rails db:migrate 2>/dev/null || bundle exec rails db:create db:migrate db:seed
# If the container has been killed, there may be a stale pid file
# preventing rails from booting up
rm -f tmp/pids/server.pid
bundle exec rails server -p 8080 -b 0.0.0.0
同ファイルに実行権限がない場合があるので、必要に応じて権限を更新します。
chmod +x bin/server-dev
Dockerfile
Rails 用の Dockerfile
を追加します。
FROM ruby:3.0.0
ENV BUNDLE_VERSION 2.2.11
ENV APP /usr/src/app
# netcat is for nc command
RUN apt-get update && apt-get install -y netcat && RUN apt-get install -y vim
RUN gem install bundler --version "$BUNDLE_VERSION"
RUN mkdir $APP
WORKDIR $APP
COPY Gemfile* $APP/
RUN bundle install
COPY . $APP/
CMD ["./bin/server-dev"]
6. docker-compose でアプリ起動
docker-compose.yml
の作成
ルート層に docker-compose.yml
を作成します。
version: '3'
services:
db:
image: mysql:8.0.20
command: --default-authentication-plugin=mysql_native_password --sql_mode=""
environment:
MYSQL_ROOT_PASSWORD: password
ports:
- '3307:3306'
backend:
build:
context: project-backend
volumes:
- ./project-backend:/usr/src/app
- ./project-backend/vendor/bundle:/usr/local/bundle
ports:
- '8080:8080'
depends_on:
- db
stdin_open: true
tty: true
environment:
DB_USERNAME: root
DB_PASSWORD: password
DB_PORT: 3306
DB_HOST: db
RAILS_MAX_THREADS: 5
RAILS_ENV: development
frontend:
build:
context: project-frontend
volumes:
- ./project-frontend:/usr/src/app
ports:
- '3000:3000'
.gitignore
の更新
bundle の保存先に指定したローカルのボリュームパスを .gitignore
に追加します。
# Ignore bundler config.
/project-backend/.bundle
+ /project-backend/vendor/bundle
動作確認
アプリを起動してみます。
docker-compose up
localhost:3000
で Next.js アプリが表示されることを確認します。
localhost:8080
で Rails アプリが表示されることを確認します。
7. Next.js と Rails の連携
Docker での開発環境で Next.js から Rails API を叩けるようにします。
ngrok のインストール
getInitialProps
など SSR 時に Rails API を叩くと ECONNREFUSED
のエラーが出るので、ここでは ngrok で発行した URL を Rails アプリに割り当てることで対処します。
インストール後、ポート 8080 で起動します。
ngrok http 8080
Rails: 環境変数の設定
上記 ngrok で準備した URL を Rails の環境変数として dotenv-rails
で管理します。
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
+ gem 'dotenv-rails'
end
docker-compose run backend bundle
環境変数として BACKEND_HOST
を設定します。
BACKEND_HOST='xxxxxxxxxxxx.ngrok.io'
.gitignore
に .env
を追加します。
.env
Rails: ホスト名の追加
開発環境の設定ファイルに開発サーバーのホスト名を追加します。
Rails.application.configure do
# .
# .
# .
# Hostname
config.hosts << 'localhost'
config.hosts << '127.0.0.1'
config.hosts << ENV['BACKEND_HOST']
end
Rails: CORS の設定
オリジン間リソース共有 (CORS) の設定をします。
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
gem 'rack-cors'
docker-compose run backend bundle
CORS の設定ファイルを更新します。
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '127.0.0.1:3000', 'localhost:3000'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
Rails: エンドポイントの作成
起動している backend のプロセス名を確認し、コンテナ内に入ります。
docker ps
docker exec -it project_backend_1 sh
モデルの作成
サンプルとして、リソース posts
を作成します。
bundle exec rails g model Post title:string body:text
bundle exec rails db:migrate
JSONPlaceholder を使って、コンソールから初期データを投入します。
bundle exec rails c
require 'net/http'
uri = URI('https://jsonplaceholder.typicode.com/posts')
data = Net::HTTP.get(uri)
posts = JSON.parse(data)
posts.each { |post| Post.create!(title: post['title'], body: post['body']) }
コントローラーの作成
GET /posts
でDBに投入したデータを JSON 形式で取得できるようにします。
Rails.application.routes.draw do
resources :posts, only: :index
end
class PostsController < ApplicationController
def index
render json: { posts: Post.limit(50) }
end
end
Next.js: API データの取得
ngrok で準備した URL をバックエンドの URL として環境変数に設定します。
NEXT_PUBLIC_BACKEND_URL='https://xxxxxxxxxxxx.ngrok.io'
getInitialProps
で Rails API からリソース posts
を取得し、タイトルを一覧として表示させます。
import Error from 'next/error';
const Home = ({ posts, statusCode }) => {
if (statusCode) {
return <Error statusCode={statusCode} />;
}
return (
<div>
<h1>POSTS</h1>
<ul>
{!posts
? null
: posts.map((post, index) => {
return <li key={index}>{post.title}</li>;
})}
</ul>
</div>
);
};
Home.getInitialProps = async (_ctx) => {
try {
const res = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}/posts`);
const data = await res.json();
return {
posts: data.posts,
};
} catch (error) {
return {
statusCode: error.response ? error.response.status : 500,
};
}
};
export default Home;
ブラウザから localhost:3000
を確認します。
一覧が表示されていれば設定は完了です。
8. Redis の設定
Rails でバックグラウンドジョブを利用するために Redis を導入します。
docker-compose.yml
で redis
サービスを追加し、backend
に REDIS_URL
を追加します。
version: '3'
volumes:
backend-sync-volume:
external: true
services:
db:
image: mysql:8.0.20
command: --default-authentication-plugin=mysql_native_password --sql_mode=""
environment:
MYSQL_ROOT_PASSWORD: password
ports:
- '3307:3306'
+ redis:
+ image: redis:alpine
+ command: ['redis-server', '--bind', '0.0.0.0', '--port', '6379']
+ ports:
+ - '6379:6379'
backend:
build:
context: project-backend
volumes:
- ./project-backend:/usr/src/app
ports:
- '8080:8080'
depends_on:
- db
stdin_open: true
tty: true
environment:
DB_USERNAME: root
DB_PASSWORD: password
DB_PORT: 3306
DB_HOST: db
RAILS_MAX_THREADS: 5
RAILS_ENV: development
CORS_ORIGINS: 'localhost:3000,127.0.0.1:3000'
+ REDIS_URL: redis://redis:6379
frontend:
build:
context: project-frontend
volumes:
- ./project-frontend:/usr/src/app
ports:
- '3000:3000'
9. Active Job の設定
ここでは Active Job のアダプタに Sidekiq を利用します。
インストール
まずは gem をインストールします。Foreman は開発環境でワーカーを立ち上げるのに利用します。
+ gem 'sidekiq'
group :development do
gem 'listen', '~> 3.3'
+ gem 'foreman'
end
docker-compose run backend bundle
Sidekiq の設定
続いて、Sidekiq の設定を追加します。
application.rb
+ config.active_job.queue_adapter = :sidekiq
routes.rb
バックグラウンドジョブのステータスを管理画面で見れるようにします。
+ require 'sidekiq/web'
Rails.application.routes.draw do
+ mount Sidekiq::Web => '/sidekiq'
resources :posts, only: :index
end
sidekiq.rb
Sidekiq.configure_client do |config|
config.redis = { size: 1 }
end
Sidekiq.configure_server do |config|
config.redis = { url: ENV['REDIS_URL'] }
end
- Heroku にデプロイする(アドオンの Redis To Go を使う)前提で設定しています。
- 設定値の算定は SidekiqHerokuRedis calculator を参考にしました。
sidekiq.yml
sidekiq -C config/sidekiq.yml
で起動できるようにします。
:concurrency: 3
:pidfile: ./tmp/pids/sidekiq.pid
:logfile: ./log/sidekiq.log
:queues:
- default
- mailers
:daemon: true
Foreman の設定
Procfile.dev
を作成し、開発環境でサーバーとワーカーを立ち上げる処理を書きます。
web: bundle exec rails server -p 8080 -b 0.0.0.0
worker: bundle exec sidekiq -C config/sidekiq.yml
開発環境の起動スクリプトで Foreman を使うように修正します。
- bundle exec rails server -p 8080 -b 0.0.0.0
+ bundle exec foreman start -f Procfile.dev
ジョブの作成
サンプルとして、PingJob
を作成します。
class PingJob < ApplicationJob
queue_as :default
def perform
puts '-- PingJob performed'
end
end
動作確認
Ctrl + C
で docker-compose のプロセスを停止し、起動し直します。
docker-compose up
web
と worker
が起動していることを確認します。
管理画面の表示
localhost:8080/sidekiq
で Sidekiq の管理画面が表示されることを確認します。
ジョブの実行
起動している backend のプロセス名を確認し、コンテナ内に入ります。
docker ps
docker exec -it project_backend_1 sh
コンソールから PingJob
を実行してみます。
bundle exec rails c
PingJob.perform_later
ログでプロセスが実行されたことを確認します。
次のステップ
デプロイ
フロントエンドを Vercel に、バックエンドを Heroku にデプロイする手順をまとめています。
GraphQL 化
バックエンド GraphQL + フロントエンド Apollo Client の構築手順をまとめています。
Discussion