Next.js + React + Firebase を GitHub Actions で CI/CD
まえがき
このブログを更新しなさすぎて、このままネットの海の藻屑になりそうなので、Qiitaに書くほどでもないけど、手を動かした記録のような記事を書いていこうかなと思います。
それで、昨年末にQiitaに書こうと思って、途中まで下書きをしていた記事です。
本題
普段、 React + Next.js + Firebase でアプリケーションを作っているのですが、同じ構成で ZEIT now(以下、now) にもデプロイできるのかなと思って、now へのデプロイを試したので、
Firebase へのCI/CD
ZEIT now へのCI/CD
をざっくりまとめます。
リポジトリ↓
デプロイ先に関係ない共通項目
アプリケーション構成
この example の index ページに、回転するロケット絵文字を付け足しただけです。ローカルで実行すると以下の画像のような感じ。
https://gyazo.com/1e3c1885d1c81391fb01f1fda9fb8822
要件
GitHub リポジトリにプッシュされた後、
テストが走り、ビルドされる(CI)
それぞれのホスティング先へデプロイされる(CD)
デプロイ後の公開ページの正常なHTTPステータス(ex. 200, 301)が確認される
デプロイ結果が Slack に通知される
GitHub リポジトリの Secrets 設定
リポジトリページの Settings → Secrets でデプロイ時に使うシークレットな変数を設定しておきます。
共通で必要な設定値、それぞれの環境で必要な設定値がありますが、行ったり来たりが面倒なので、先にすべて書いておきます。workflowファイル内の変数を変更すれば、以下の値と全く同じでなければならないというわけではないです。
デプロイ先関係ない共通な設定値
SLACK_WEBHOOK_URL
Slack通知用のwebhook URL です
Firebase で必要な設定値
PROJECT_ID
Firebase プロジェクトの Project ID の値
FIREBASE_API_KEY
Firebase プロジェクト の Project Settings の Web API Key の値
FIREBASE_TOKEN
firebase-tools の firebase login:ci コマンドで取得できるCI用のトークン(詳細) now で必要な設定値
ZEIT_TOKEN
FirebaseへのCI/CD
package.json の scripts に predeploy スクリプトの追加
後述する now へのデプロイ時には必要ではなく、Firebase へのデプロイのときのみ必要です。端的に言うと、デプロイ前のビルドです。(Firebaseでホスティングする都合、コンパイルしたコードをよしなにディレクトリに入れたりする必要がある)
example ですでにある preserve でも代用できそうではありますが、余分に npm i が入っているかもしれません。
code:package.jsonのscripts抜粋
"scripts": {
...
"preserve": "npm run build-public && npm run build-functions && npm run build-app && npm run copy-deps && npm run install-deps",
...
"predeploy": "npm run build-app && npm run build-public && npm run build-functions && npm run postbuild-app && npm run copy-deps",
...
},
firebase.json の変更
前段で、 predeploy スクリプトを作ったので、 デプロイ前の作業は predeploy を走らせたら、終わっているはずです。なので、 firebase.json はビルドしたものをデプロイするだけにさせます。
code:firebase.json
{
"functions": {
"source": "dist/functions"
},
"hosting": {
"public": "dist/public",
"rewrites": [
{
"source": "**/**",
"function": "nextApp"
}
]
}
}
firebase.json の predeploy を使用するかどうかは、好みの問題ですが、後で GitHub Actions に組み込むときに、ビルドでコケたのか、デプロイでコケたのか分かりやすくするために、 package.json のスクリプトに主導権を持たせています。
GitHub Actions のワークフロー
Test
yarn test で回しているだけです。
Deploy
yarn predeploy した後、firebase deploy を使ってデプロイします。
Ping(Health Check)
srt32/uptime という action を使って、公開ページのHTTPステータスを確認します。
code:.github/workflows/cd-firebase-hosting.yml
name: CD (firebase)
on:
push:
branches:
- master
- feature/firebase
env:
SLACK_CHANNEL: "#develop"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@master
- name: Setup node
uses: actions/setup-node@v1
with:
node-version: 10.x
- name: Cache node_modules
uses: actions/cache@v1
id: cache
with:
path: ./node_modules
key: ${{ runner.os }}-yarn-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install
run: yarn install
- name: Test
run: yarn test
- name: Slack Notification
uses: homoluctus/slatify@master
if: failure() || cancelled()
with:
type: ${{ job.status }}
job_name: "*Test*"
mention: "here"
mention_if: "failure"
channel: ${{ env.SLACK_CHANNEL }}
url: ${{ secrets.SLACK_WEBHOOK_URL }}
commit: true
token: ${{ secrets.GITHUB_TOKEN }}
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@master
- name: Setup node
uses: actions/setup-node@v1
with:
node-version: 10.x
- name: Cache node_modules
uses: actions/cache@v1
id: cache
with:
path: ./node_modules
key: ${{ runner.os }}-yarn-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install
run: yarn install
- name: Build
run: yarn predeploy
env:
FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }}
FIREBASE_PROJECT_ID: ${{ secrets.PROJECT_ID }}
- name: Deploy to Firebase
run: yarn firebase use --add ${{ secrets.PROJECT_ID }} && yarn deploy
if: success()
env:
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
- name: Slack Notification
uses: homoluctus/slatify@master
if: failure() || cancelled()
with:
type: ${{ job.status }}
job_name: "*Deploy to the site*"
mention: "here"
mention_if: "failure"
channel: ${{ env.SLACK_CHANNEL }}
url: ${{ secrets.SLACK_WEBHOOK_URL }}
commit: true
token: ${{ secrets.GITHUB_TOKEN }}
ping_site:
needs: deploy
runs-on: ubuntu-latest
steps:
- name: Check the site
uses: srt32/uptime@master
with:
url-to-hit: ${{ env.SITE_URL}}
expected-statuses: "200,301"
- name: Slack Notification
uses: homoluctus/slatify@master
if: always()
with:
type: ${{ job.status }}
job_name: "*Check the site ${{ env.SITE_URL }} *"
mention: "here"
mention_if: "failure"
channel: ${{ env.SLACK_CHANNEL }}
url: ${{ secrets.SLACK_WEBHOOK_URL }}
commit: true
token: ${{ secrets.GITHUB_TOKEN }}
使用したサードパーティの action
GitHub Actions の構文等の説明は 公式ドキュメント に分かりやすく書かれているので、そちらをご参照いただければです。 ここでは、使用したサードパーティの Actions を紹介します。
homoluctus/slatify
任意のテキストで Slack に通知をしてくれる action です。
個人的には、
設定項目があまり多くなく、シンプル
通知される分量があまり多くない(通知スペースが不要な情報で埋められたくないですよね…)
コミット、ワークフローへのリンクが有る
https://gyazo.com/b0ccefdf56b41aeecf420ae188241e4b
firebase コマンドを実行するための action です。
記事を書きながら、ワークフローを見直してたら、使わなくてもよさそうだったので、外しました。
firebase deploy 時に、ルートディレクトリに .firebaserc があると、その情報(エイリアス)でデプロイ先が変更されます。(詳細 )ただ、.firebaserc にはプロジェクトIDを書き込むので、バージョン管理からは外したいということで、firebase deploy をする前に firebase use --add でデプロイ先を直接指定しています。 デプロイ後、公開ページへのアクセスをして、HTTPステータスを確認してくれる actionです。
ちなみに、この前、別のプロジェクトで使っていて分かったのですが、内部では node.js の https でgetしているので、httpsなURLでないとコケます。
CDされたデモページ
ZEIT now へのCI/CD
next.config.js に serverless target の分岐を追加
now では target が serverless ではないと行けないので、 next build 時の動作を分岐してます。
code: next.config.js
if (process.env.NOW) {
module.exports = {
distDir: "../../.next",
target: "serverless"
};
} else {
module.exports = {
distDir: "../../dist/functions/next"
};
}
package.json の scripts に build スクリプトの追加
code:package.jsonのscripts抜粋
"scripts": {
...
"build": "cross-env NOW=true next build \"src/app\"",
...
},
GitHub Actions のワークフロー
Test
yarn test で回しているだけです。
Deploy
now でデプロイします。
Ping(Health Check)
srt32/uptime という actionを使って、公開ページのHTTPステータスを確認します。
code:.github/workflows/cd-now.yml
name: CD (now)
on:
push:
branches:
- master
- feature/now
env:
SLACK_CHANNEL: "#develop"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@master
- name: Setup node
uses: actions/setup-node@v1
with:
node-version: 10.x
- name: Cache node_modules
uses: actions/cache@v1
id: cache
with:
path: ./node_modules
key: ${{ runner.os }}-yarn-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install
run: yarn install
- name: Test
run: yarn test
- name: Slack Notification
uses: homoluctus/slatify@master
if: failure() || cancelled()
with:
type: ${{ job.status }}
job_name: "*Test*"
mention: "here"
mention_if: "failure"
channel: ${{ env.SLACK_CHANNEL }}
url: ${{ secrets.SLACK_WEBHOOK_URL }}
commit: true
token: ${{ secrets.GITHUB_TOKEN }}
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@master
- name: Setup node
uses: actions/setup-node@v1
with:
node-version: 10.x
- name: Cache node_modules
uses: actions/cache@v1
id: cache
with:
path: ./node_modules
key: ${{ runner.os }}-yarn-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install
run: yarn install
- name: Deploy to ZEIT now
run: yarn now -t ${{ secrets.ZEIT_TOKEN }} --prod
if: success()
- name: Slack Notification
uses: homoluctus/slatify@master
if: failure() || cancelled()
with:
type: ${{ job.status }}
job_name: "*Deploy to the site*"
mention: "here"
mention_if: "failure"
channel: ${{ env.SLACK_CHANNEL }}
url: ${{ secrets.SLACK_WEBHOOK_URL }}
commit: true
token: ${{ secrets.GITHUB_TOKEN }}
ping_site:
needs: deploy
runs-on: ubuntu-latest
steps:
- name: Check the site
uses: srt32/uptime@master
with:
url-to-hit: ${{ env.SITE_URL}}
expected-statuses: "200,301"
- name: Slack Notification
uses: homoluctus/slatify@master
if: always()
with:
type: ${{ job.status }}
job_name: "*Check the site ${{ env.SITE_URL }} *"
mention: "here"
mention_if: "failure"
channel: ${{ env.SLACK_CHANNEL }}
url: ${{ secrets.SLACK_WEBHOOK_URL }}
commit: true
token: ${{ secrets.GITHUB_TOKEN }}
使用したサードパーティの action
以下、2つの action については Firebase と同じでなので省略します。
homoluctus/slatify
srt32/uptime
now コマンドを実行するための action です。
こちらは、 w9jds/firebase-action と違い、GitHubの一部の機能と連携している部分もあるので、そういうのを利用したいなら、使うのもよさそうな感じではあります。
今回は、見直し中に使わなくてもよさそうだったので、外しました。
CDされたデモページ
まとめ
Firebase
ZEIT now
に GitHub Actions を使ってCI/CDしました。
最後に
途中で、サードパーティの action を外していた箇所についてです。
基本的に、ワークフロー内でやりたいことが薄い場合は、サードパーティの action を使わないほうがベターだと思います。CI/CDツールは様々出てきていますが、ワークフロー内のジョブがツール特有のもの(今回のサードパーティ action等)で大半が埋められていくと、ベンダーロックみたいなことになって、CI/CD環境を移行しないといけないときに辛かったり、メンテ自体もブラックボックスがあって辛かったり等のシチュエーションが考えられます。
さらに、ワークフロー自体が軽めのドキュメント(自分の過去のプロジェクトのデプロイ方法を思い出すときとか…)にもなりうる場合もあるので、シンプルでオープンな感じにしておきたいですね。