Next.jsプロジェクトをGitHub ActionsでFirebase Hostingに自動デプロイする

## 本稿の目指すところ

タイトルの通りですが、Next.js(SSG)で作成したNext.js公式のブログテンプレートサンプルをGitHub ActionsでFirebase Hostingに自動デプロイできるようにします。

細かいNext.jsやGit、Firebaseの設定などは一旦置いておいて、とりあえず私のような初学者向けにサクッとCI/CD環境を構築することを目的としました。

## 構成/環境

| 名称| 役割 | 補足 |
|---|---|---|
|React(Next.js)|画面開発|SSGで実装|
|GitHub|バージョン管理||
|GitHub Actions|CI/CD|リポジトリ内のソフトウェア開発フローを自動化できる|
|Firebase Hosting|ホスティングサービス|無料プランを利用します|

- macOS Monterey v12.6.1 (Intel Chip)
- node.js v16.15.0
- yarn v1.22.18

## 1. Next.jsプロジェクトの作成

https://vercel.com/templates/next.js/blog-starter-kit

### 1-1. blog-starterテンプレートでプロジェクト作成

```zsh:zsh
# example-blog-sterterは好きなプロジェクト名でOK
% yarn create next-app --example blog-starter example-blog-starter

# 以下が表示されたら作成完了
Success! Created example-blog-starter at /xxxxxxx/example-blog-starter
```

### 1-2. ローカル環境起動確認

```zsh:zsh
# 作成したプロジェクトへ移動
% cd example-blog-starter/

# ローカル環境を起動
% yarn dev

# 以下が表示されたら起動成功
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
```

http://localhost:3000/ へアクセスし、念の為[Next.js公式で用意されているデモ画面](https://next-blog-starter.vercel.app/)と同じものが表示されることを確認する。

### 1-3. 静的HTMLをエクスポート

現状のbuildスクリプトだと静的HTMLの出力ができずサーバー側にnode.jsが必要となるため
buildスクリプトに`next export`を追加し、静的HTMLのエクスポートを可能にする。
デフォルトでは`./out`にビルドされた資材がエクスポートされます。
```diff_json:./package.json

{
 "scripts": {
    "dev": "next",
-   "build": "next build",
+   "build": "next build && next export",
  },
}
```

devDependenciesにserveというパッケージをインストールし
ローカルサーバーで静的ファイルをホスティングし動作確認できるようにする。
```zsh name:zsh
% yarn add -D serve
```

build & serveを実行するため、serveスクリプトを追加する。
```diff_json:./package.json
{
 "scripts": {
    "dev": "next",
    "build": "next build && next export",
+   "export": "yarn build && serve ./out",
  },
}
```

このまま`yarn serve`をすると以下のように「画像の最適化に利用しているNext.jsのデフォルトローダーがnext exportに適してない」と怒られるので、`./next.config.js`を作成し画像最適化を行わないように設定を追加する。
```zsh:zsh
% yarn serve

Error: Image Optimization using Next.js’ default loader is not compatible with `next export`.
```

```javascript:next.config.js
module.exports = {
  images: {
    unoptimized: true, // 画像最適化を行わない
  },
};
```

改めて`yarn serve`を行い静的ビルド&ローカルサーバー立ち上げを行い、画面確認をする。
```zsh:zsh
% yarn serve

# 以下が表示されたらhttp://localhost:3000でSSGされた画面を確認できる。
Export successful. Files written to /Users/user_name/xxxxx/example-blog-starter/out

   ┌───────────────────────────────────────────┐
   │                                           │
   │   Serving!                                │
   │                                           │
   │   - Local:    http://localhost:3000       │
   │   - Network:  http://xxxxxxxxxxxxx:3000   │
   │                                           │
   │   Copied local address to clipboard!      │
   │                                           │
   └───────────────────────────────────────────┘
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/ecc24792-e5d1-6a84-ab44-fc2765b15548.png)

以上でNext.jsのプロジェクト作成は完了です。

---

## 2. GitHubリポジトリの作成と資材Push

作成したNext.jsプロジェクトをGitHubで管理できるようにします。

### 2-1. リポジトリ作成

GitHubにログインし、RepositoriesのNewから今回のプロジェクト用リポジトリを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/cd36d946-0aa0-a4cd-e241-86b193448fcd.png)

- Repository name: 自由ですがわかりやすいのでプロジェクト名を使っています
- Description: 書かなくてもOK
- Public/Private: どちらでもOK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/fdfc403b-2f4e-1aa2-d6d9-e1b165ecce74.png)

Create repository を押下してリポジトリを作成し、リポジトリのURLをコピーしておく。
(https://github.com/user_name/repository_name)

### 2-2. 資材をリポジトリへプッシュ

```zsh:zsh
# リモートリポジトリを同期させる
% git remote add origin ${コピーしたリポジトリのURL}

# 資材をstagingへ登録
% git add .

# stagingの資材をコミット
% git commit -m 'first commit with next.js project'

# リモートリポジトリへローカルリポジトリの状態をプッシュ
% git push -u origin main

# 以下が表示されたらOK
branch 'main' set up to track 'origin/main'.
```

以上を実施しリポジトリのページをリロードするとコミットが確認できます。

## 3. Firebaseプロジェクトの作成とFirebase CLIのインストール

### 3-1. Firebaseプロジェクトを追加

Firebaseのコンソールへアクセスしプロジェクト作成画面を表示します。
https://console.firebase.google.com/

プロジェクトを追加を押下し、今回のアプリをホスティングするためのプロジェクトを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/cfa527e7-2077-d35d-ca61-420b69ea6df3.png)

プロジェクト名はリポジトリ名と同じ名称を入力するとわかりやすくて良きです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/b72bc8b5-7269-a4ed-3b96-c1b99627b312.png)

Googleアナリティクスは今回使わないためオフにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/d7a7bfad-9b07-9086-78ed-14dd0083f744.png)

プロジェクト作成に成功したら続行を選択し、プロジェクト概要へ移動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/b7157b77-9fd9-b382-7501-07aebf865442.png)

今回はWebアプリを作成するため、`</>(ウェブ)`を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/5b69e24a-0f45-1548-0744-07f7bd7e3ffa.png)

Firebaseコンソールで管理する名称としてニックネームを付けます。  
無難にリポジトリ名と同じ名称をつけましょう。
「このアプリの **Firebase Hosting** も設定します。」にチェックを入れ、アプリを登録。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/b2b2557f-13ad-b24c-fb7b-1f409ee90263.png)

### 3-2. Firebase SDKをインストール

以下のコマンドか、画面に表示されているnpmコマンドを叩いてインストールしましょう。

```zsh:zsh
# firebase sdkをdevDependenciesへ追加
% yarn add firebase
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/4670bab5-fc55-127b-bbbb-6eca35fcd84d.png)

`firebaseConfig`については今回は特に利用しないため割愛します。

### 3-3. Firebase CLIをインストール

こちらも以下のコマンドか、画面に表示されているnpmコマンドを叩いてインストールしましょう。

```zsh:zsh
# firebase cliをdevDependenciesへ追加
% yarn add firebase-tools
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/808f6523-880f-27f5-f4b4-9959915e97e9.png)

4の「Firebase Hostingへのデプロイ」は追って行うため、「コンソールに進む」を押下して本ステップは完了となります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/ea6bfd1f-4a4f-4e66-6744-cbebe3b365c9.png)

## 4. GitHubActionsの設定

先程インストールしたFirebase CLIを使って、ローカルプロジェクトをFirebaseプロジェクトに連携します。

### 4-1. Firebaseにログイン

プロジェクトルートにて以下コマンドでfirebaseにログインします。

```zsh:zsh
# firebaseにログイン
% yarn firebase login

# すると以下の質問が表示されるため、Yを入力しエンターを押す
i  Firebase optionally collects CLI and Emulator Suite usage and error reporting information to help improve our products. Data is collected in accordance with Google’s privacy policy (https://policies.google.com/privacy) and is not used to identify you.

? Allow Firebase to collect CLI and Emulator Suite usage and error reporting information? (Y/n) 
```

ブラウザでFirebase CLIでログインするアカウントの選択画面が立ち上がるので、任意のアカウントを選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/8eb50637-954e-6e9b-b259-5646bf7f7f41.png)

選択したアカウントへのアクセスリクエストを許可

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/fdf08966-cd32-febd-7a17-ec4007f18646.png)

`Woohoo! Firebase CLI Login Successful`と表示されたらログイン完了です。
`yarn firebase login`を実行したshellの方にも、`Success! Logged in as 選択したアカウント`と表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/c3288cb9-b8c2-52fe-77cd-de6287c2fe83.png)

### 4-2. Firebaseプロジェクトの初期設定

firebaseプロジェクトの初期設定のため、プロジェクトのルートディレクトリにて以下を実行します。
```zsh:zsh
% yarn firebase init
```

するとFIREBASEのデカ文字と共にいくつかの質問が表示されるため、順に答えていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/a3618d74-707b-8224-4280-08758bb2f062.png)


```zsh:zsh
# Q: このディレクトリに対して、どのFirebaseの機能を設定するか？Spaceキーを押して機能を選択し、Enterで選択内容を確定します。
? Which Firebase features do you want to set up for this directory? Press Space to select features, then 
Enter to confirm your choices. (Press <space> to select, <a> to toggle all, <i> to invert selection, and 
<enter> to proceed)

# 以下を選択 (ホスティングを行います。Firebase Hosting用のファイルを設定し、GitHub Actionのデプロイを設定する)
=> Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys


=== Project Setup

# Q: まず、このプロジェクトディレクトリをFirebaseのプロジェクトに関連付けます。
# firebase use --addを実行すると複数のプロジェクトエイリアスを作成することができますが、今回はデフォルトのプロジェクトを設定しましょう。
? First, let’s associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add, 
but for now we’ll just set up a default project.

=> Use an existing project # 既存のプロジェクトを使用する を選択


# Q: 現在のディレクトリのデフォルトのFirebaseプロジェクトを選択してください。
? Select a default Firebase project for this directory:
=> example-blog-starter (example-blog-starter) # 先程作成済みのFirebaseプロジェクトを選択

=== Hosting Setup

# firebase deployでアップロードしたいHosting assetsが含まれるパブリックディレクトリについて、もしassetsに対してビルドプロセスがある場合はビルド後assetsのアウトプット先ディレクトリを指定してください。
Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build’s output directory.

# Q: パブリックディレクトリとしてどれを利用しますか？
? What do you want to use as your public directory? (public)
=> out # SSGのためにnext exportで ./out に出力するため


# Q: SPAとしてすべてのURLを/index.htmlに書き換えますか？
? Configure as a single-page app (rewrite all urls to /index.html)? (y/N) 
=> No # SPAではないため


# Q: GitHubで自動ビルドとデプロイを設定しますか？
? Set up automatic builds and deploys with GitHub? (y/N)
=> Yes


# Q: 既に存在するout/404.htmlを上書きするか？
? File out/404.html already exists. Overwrite? (y/N)
=> No


# Q: 既に存在するout/index.htmlを上書きするか？
? File out/index.html already exists. Overwrite? (y/N)
=> No # ここでGitHubアカウント認証を求められたら許可してください。


# Q: どのGitHubリポジトリに対してGitHubのワークフローを設定しますか？
? For which GitHub repository would you like to set up a GitHub workflow? (format: user/repository)
=> git_user_name/example-blog-starter # ${自分のGitHubアカウント}/${リポジトリ}を入力

# するとGitHub Actionsの設定 & Firebase連携に必要な環境変数やkeyなどの設定を自動でやってくれます。
✔  Created service account github-action-xxxxxxxx with Firebase Hosting admin permissions.
✔  Uploaded service account JSON to GitHub as secret FIREBASE_SERVICE_ACCOUNT_EXAMPLE_BLOG_STARTER.


# Q: デプロイする前にビルドスクリプトを実行するように設定しますか？
? Set up the workflow to run a build script before every deploy? (y/N)
=> Yes


# Q: どのスクリプトを使ってビルドを実行しますか？
? What script should be run before every deploy? (npm ci && npm run build) 
=> yarn install && yarn build


# Q: プルリクエストがマージされたら自動デプロイを実行するように設定しますか？
? Set up automatic deployment to your site’s live channel when a PR is merged? (Y/n)
=> Yes


# Q: 本番デプロイに利用するブランチ名は何ですか？
? What is the name of the GitHub branch associated with your site’s live channel?
=> main # メインブランチ名がmasterの場合はmasterにすること


# 以下が表示されたら初期設定完了
✔  Firebase initialization complete!
```

## 5. Firebaseへデプロイ

Firebase(+GitHub Actions)の設定が完了したので、いよいよデプロイを実行します。

```zsh:zsh
# デプロイ実行
% yarn firebase deploy

=== Deploying to 'example-blog-starter'...

i  deploying hosting
i  hosting[example-blog-starter]: beginning deploy...
i  hosting[example-blog-starter]: found 38 files in out
✔  hosting[example-blog-starter]: file upload complete
i  hosting[example-blog-starter]: finalizing version...
✔  hosting[example-blog-starter]: version finalized
i  hosting[example-blog-starter]: releasing new version...
✔  hosting[example-blog-starter]: release complete

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/project_name/overview
Hosting URL: https://project_name.web.app
```

`Hosting URL: https://project_name.web.app`のURLでデプロイされたサイトにアクセスできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/44e04ef5-a001-aae1-5ad2-222c3ed035ac.png)

## 6. ここまでの作業をコミット

作業の中で色々とファイルが更新、追加されているので、ここまでの作業進捗をコミットします。

```zsh:zsh
# 差分をステージングへ追加
% git add .

# ステージングの資材を確認
% git status

On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   .firebase/hosting.b3V0.cache
        new file:   .firebaserc
        new file:   .github/workflows/firebase-hosting-merge.yml
        new file:   .github/workflows/firebase-hosting-pull-request.yml
        new file:   firebase.json
        modified:   package.json
        modified:   yarn.lock

# リモートへコミット
% git commit -m 'init deployment workflow with firebase'

# リモートへプッシュ
% git push
```

## 7. 最新資材の自動デプロイ確認

firebaseの初期設定を通してにmainブランチへのマージをトリガーに自動デプロイが走るGitHub Actions workflowが組まれているため、差分を作って自動デプロイの動作確認をしてみます。

### 7-1. 直接mainブランチへマージする場合
```diff_tsx:./components/intro.tsx
// TOPページのタイトルを適当に変更
<h1 className="text-5xl md:text-8xl font-bold tracking-tighter leading-tight md:pr-8">
-  Blog.
</h1>

<h1 className="text-5xl md:text-8xl font-bold tracking-tighter leading-tight md:pr-8">
+  Blog with Firebase and GitHub Actions.
</h1>
```

```zsh:zsh
# 作業ブランチを作成
% git checkout -b feature-change-title

# 差分の追加〜リモートへブランチプッシュ
% git add .
% git commit -m '[feat] change title text'
% git push --set-upstream origin feature-change-title

# mainブランチをチェックアウト
% git checkout main

# mainブランチへfeature-change-titleブランチをマージ
% git merge feature-change-title

# 修正をマージしたmainブランチをリモートへプッシュ
% git push
```

するとGitHubがmainブランチの更新を検知して、GitHubActionsのworkflowが起動します。
以下画面で黄色になっているworkflowが実行中のもので、完了することで緑のチェック状態となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/1db5d669-850c-1a1e-17fa-5fbaac5d98eb.png)

workflowが完了したら修正内容を含んだ最新のmainブランチがデプロイ済みとなるので、
画面へアクセスして確認できるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/7383728f-eba1-c51b-ebe4-0db4b6af2058.png)

### 7-2. GitHubでPRを立てPreviewを見る場合

GitHub上で開発ブランチからmainブランチへのPRを作成すると
マージ前にPreview用URLが出力されるため、レビューの段階で画面確認ができとても便利です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283305/aabc15f1-5cf2-9956-1ae7-47d56567a0df.png)

以上で自動ビルド＆デプロイの確認完了となります。

## 雑記

個人的にはとりあえずやってみる派の人間なので、Firebase CLIが諸々の設定をやってくれてサクッと試せるのが手軽で良いですね。細かな設定は適宜必要になったタイミングで読み込んで対応していけば良いかなと。
今後は`react-testing-library`や`VRT`を組み込んでテスト周りの自動化にも挑戦したいですね。

以上、拙い解説ですが読んで頂きありがとうございました！
誤字脱字、指摘などありましたらコメント頂けると幸いです。