【Firebase】Hosting で複数のサイトをデプロイする設定方法の全手順

## 概要
Firebase Hosting のサイトが複数ある場合、コマンドで一気に全サイトにデプロイしたり、個別デプロイをコマンドで簡略化したい時の設定内容について（いつも忘れてしまうので、その備忘録）。

::: note
サンプルのファイル構成、サイト構成を例にあげて手順を解説します。
:::

詳細については、[公式のドキュメント](https://firebase.google.com/docs/hosting/multisites?hl=ja&authuser=0#set_up_deploy_targets) を参考にしてください。

### 前提
- １つのプロジェクトに対して、ローカルマシンから複数のサイトをデプロイする前提の内容です（複数プロジェクトに対してではない）。
- Firebase Hosting には、既にコンソール等から、ターゲットとなる複数のサイトが追加されている前提とします。
  - 例） Hosting に追加したサイト **「demo-app-admin」「demo-app-user」**
- 以下のファイル構成でアプリが２つある状態を想定します。
  - 例） Hosting にデプロイしたいアプリ **「ui-admin」「ui-user」**

```bash:ソースコードの構成
app
  ├ ui-admin  # 管理者画面のアプリケーション
  ├ ui-user   # ユーザ画面のアプリケーション
  ├ .firebaserc
  ├ .gitignore
  ├ firebase.json
  ├ package-lock.json
  ├ package.json
  └ README.md
```

::: note warn
注意点
- 今回、私自身は **"Angular"** でアプリを作成しましたが、別に Vue/Nuxt や React/Next など他のアプリケーションでも構いません
- ただし、SSR などのプリレンダリングを採用したアプリを使用している場合、Firebase 側も SSR 等に対応した状態で初期設定済みである必要があります 
:::

## 手順

### 変更前の設定ファイル
設定前の firebase の設定ファイルは以下の様になっています（firebase init コマンドで初期化したばかりの状態の例）。

`.firebaserc` ファイル
```js:.firebaserc
{
  "projects": {
    "default": "yurugo-df17f"
  }
}
```

`firebase.json` ファイル
```json:firebase.json
{
  "hosting": {
    "source": "hosting",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "frameworksBackend": {
      "region": "asia-east1"
    }
  }
}
```

### デプロイ ターゲットを追加
まず、デプロイターゲットの設定をファイルに追加します。

firebase コマンドには、追記用のコマンドが用意されています。
```bash
firebase target:apply hosting {任意のターゲット名} {実在するサイト名}
```
補足すると以下の通りです。

- 【１つ目】**TARGET_NAME**: デプロイ先の Hosting サイトに付ける任意の名前
  - 好きな名前で構いませんが、一意でなければいけません
- 【２つ目】**RESOURCE_IDENTIFIER**: 実際に Firebase Hosting に存在するサイトの SITE_ID
  - `xxxxx.web.app` の `xxxxx` の部分のことです


実際に追加するコマンド例
```bash
# 管理者用のサイトを設定に追加
firebase target:apply hosting admin-site demo-app-admin

# ユーザ用のサイトを設定に追加
firebase target:apply hosting user-site demo-app-user
```

まず、**admin-site** の設定追加コマンドだけ実行すると、`.firebaserc` のみ更新されて、下記のようになります。

```diff_javascript:.firebaserc
{
  "projects": {
    "default": "demo-app-123456"
  },
+ "targets": {
+   "demo-app-123456": {
+     "hosting": {
+       "admin-site": [
+         "demo-app-admin"
+       ]
+     }
+   }
+ },
+ "etags": {}
}
```

**user-site** の設定追加コマンドも実行すると、下記のようになります。
```diff_javascript:.firebaserc
{
  "projects": {
    "default": "demo-app-123456"
  },
  "targets": {
    "demo-app-123456": {
      "hosting": {
        "admin-site": [
          "demo-app-admin"
        ],
+       "user-site": [
+         "demo-app-user"
+       ]
      }
    }
  },
  "etags": {}
}
```

### デプロイ ターゲットにリソースを紐づける設定
次は、さっき設定したデプロイの「ターゲット」と、ソースコードの「リソース」を紐付けます。
`firebase.json` を以下のように修正しましょう。

```json:firebase.json
{
  "hosting": [
    {
      "target": "admin-site",
      "source": "ui-admin",
      "ignore": [
        "firebase.json",
        "**/.*",
        "**/node_modules/**"
      ],
      "frameworksBackend": {
        "region": "asia-east1"
      }
    },
    {
      "target": "user-site",
      "source": "ui-user",
      "ignore": [
        "firebase.json",
        "**/.*",
        "**/node_modules/**"
      ],
      "frameworksBackend": {
        "region": "asia-east1"
      }
    }
  ]
}
```

補足すると以下の通りです。

- **"hosting"** を配列に変更する
- **"target"**: 先ほど決めた任意のターゲット名
- **"source"**: デプロイ先となるアプリケーションを設定
  - source の代わりに **"public"** を設定することも可能
  - public の場合、`"public": "ui-admin/dist"` のように、各アプリのビルド先ディレクトリを指定

::: note warn
注意： **Next 14** で試したら、"public" だとうまくいかなかったので、"source" の方が良いかもしれません（うまくいく方法がわかれば追記します🙏）
:::

### デプロイ実行コマンド
まず、デプロイする前に、対象のソースコードは全てビルドしておきましょう。デプロイするリソースが存在しないとエラーになります。

firebase コマンドでデプロイするコマンドは以下です。

```bash
# 全てデプロイする場合
firebase deploy --only hosting

# サイト毎に個別デプロイする場合
firebase deploy --only hosting:admin-site
```

firebase コマンド（firebase-tools）がグローバルではなく node_modules などローカルに入っている場合は、コマンドの先頭に `npx` をつければ OK です。

### npm コマンドを作って楽にする
上記のコマンドでも良いのですが、せっかく package.json があるので、npm のスクリプトにまとめてしまいましょう。

`npm run` コマンドで簡単に実行できるようにしておきます。
下記が、"scripts" への追記例です。
```json:package.json
{
  "scripts": {
    "deploy:all": "firebase deploy",
    "dep:host": "firebase deploy --only hosting",
    "dep:admin": "firebase deploy --only hosting:admin-site",
    "dep:user": "npx firebase deploy --only hosting:user-site"
  },
  "dependencies": {
    "firebase": "^10.7.1"
  }
}
```

- `firebase deploy`: Hosting だけでなく、ローカルにある全ての Firebase リソースをデプロイしちゃいます（ソースコードや設定があれば）ので、ご注意ください。この設定は無くても良いかもしれません。
- `firebase deploy --only hosting`: Firebase Hosting の全サイトにデプロイします。まとめてやる時はこちら
- `firebase deploy --only hosting:{サイト名}` 個別のサイトだけにデプロイする時に追加います

スクリプトの引数名は自由です。私は「deploy:admin」が打つの面倒なので、少しだけ短くしました😂

さて、複数デプロイの設定は、以上で完了です！

## デプロイ実行
では、実際に作成したコマンドでデプロイを実行してみます。

::: note
初回だけは、デプロイ完了までに結構時間がかかりますが、2回目以降は結構早く終わります。
:::

#### firebase deploy コマンドでデプロイ
※ あくまでも私の設定した名前でのコマンド例です。

```bash
# 個別デプロイ（管理者画面）
firebase deploy --only hosting

# 全サイトデプロイ
firebase deploy --only hosting:admin-site
```

#### npm run コマンドでデプロイ
※ あくまでも私の設定した名前でのコマンド例です。

```bash
# 個別デプロイ（管理者画面）
npm run dep:admin

# 全サイトデプロイ
npm run dep:host
```
npm スクリプトに定義しただけなので、内部でやってることは firebase コマンドと全く同じです。

#### ブラウザでデプロイの成功確認
いずれのコマンドでも、以下のようにコンソール表示されたら成功です（全てのサイトにデプロイした時の例）。
```bash
# デプロイに成功したメッセージ
✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/demo-app-123456/overview
Hosting URL: https://demo-app-admin.web.app
Hosting URL: https://demo-app-user.web.app
```

URL が表示されるので、実際にアクセスしてみましょう。

**管理者画面**
- Hosting サイト名: **「demo-app-admin」**
- ソースコードのアプリ名: **「ui-admin」**

![ui-admin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/787582/8a82eb06-3924-915f-7c6e-8caea58ebec4.png)

**ユーザ画面**
- Hosting サイト名: **「demo-app-user」**
- ソースコードのアプリ名: **「ui-user」**

![ui-user.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/787582/9879f898-c804-03a6-95fa-96479ffd16be.png)

無事、デプロイまで完了できました 🙌✨
デプロイの実行履歴はコンソール画面から確認しましょう！

### よくあるエラー・失敗例
SSR/Pre-rendering の機能を有効にしたアプリケーションをデプロイする場合、下記のエラーのようにデプロイに失敗することがあります。

`Error: Failed to list functions for {プロジェクト名}`

```bash
Application bundle generation complete. [6.878 seconds]

up to date in 1s

29 packages are looking for funding
  run `npm fund` for details

# Firebase Functions でエラーが発生
Error: Failed to list functions for demo-app-123456
```

これは、**Firebase Functions** が有効化されていないからかもしれません。SSR/Pre-rendering を採用したアプリケーションの場合、Firebase では Functions も使用することになります。

なので、Firebase Functions が正常に使える状態かを確認しましょう。

::: note warn
**注意**
そもそも、Firebase Functions は **"Spark" プラン（無料枠のみのプラン）では利用できない**（執筆時現在）ので、**"Blaze" プラン（無料枠付きの従量課金プラン。要クレジットカード登録）** 以上にアップグレードしておく必要があります。

また、実行可能な Functions も最低１つ登録しておかないと、Functions のデプロイでエラーになります。`firebase init` で Functions 設定を追加し、サンプルで生成される関数をコメントインしておけば OK です。
:::

#### 対応策
- まず、料金プランを「Blaze」以上にする
- `firebase init` コマンドを再実行し「Functinos」を選んで有効にする
- ローカルに自動生成された functions/src/index[.js|.ts|.py] のサンプル関数を有効にしておく（Hello, world 関数的なやつ）

::: note
`firebase init` で既存の Functions 以外の設定は消えたりしませんのでご安心を 🙌
:::

以上で、Hosting や何かしらのデプロイコマンドを実行すれば、Functions も合わせてデプロイ処理してくれます（`ssrdemoappadmin`, `ssrdemoappuser` みたいな SSR 用の関数が出来上がってます）。

## まとめ

- `.firebaserc` ファイルにターゲットサイトの設定を追加
- `firebase.json` ファイルに、ターゲットと紐づくリソースの設定を追加
- 必要に応じて、`package.json` の `"scripts"` にデプロイコマンドを追加

ご参考になれば幸いです 🙌✨