🙆

Firebase App HostingにNext.jsがデプロイされる仕組み

2024/05/15に公開

Firebase CLIのNext.jsデプロイ対応について調べる」ではCloud FunctionsにカスタムビルドしたNext.jsアプリがデプロイされていたが、Cloud Functions (2nd gen)でCloud RunベースになったのでNext.jsのデプロイもApp HostingでCloud Run版になった

基本的なデプロイまでの手順以下のドキュメントにある。本記事ではこれを参考に内部動作を調べる

https://firebase.google.com/docs/app-hosting

firebase-tools

https://github.com/firebase/firebase-tools

firebase-cliの最新版に以下のコマンドが追加されている

❯ firebase --help | grep apphosting:

  apphosting:backends:list [options]                             list Firebase App Hosting backends
  apphosting:backends:create [options]                           create a Firebase App Hosting backend
  apphosting:backends:get [options] <backend>                    print info about a Firebase App Hosting backend
  apphosting:backends:delete [options] <backend>                 delete a Firebase App Hosting backend
  apphosting:secrets:set [options] <secretName>                  create or update a secret for use in Firebase App Hosting
  apphosting:secrets:grantaccess [options] <secretName>          grant service accounts permissions to the provided secret
  apphosting:secrets:describe <secretName>                       Get metadata for secret and its versions.
  apphosting:secrets:access <secretName[@version]>               Access secret value given secret and its version. Defaults to accessing the latest version.

これを直接実行しなくても管理コンソールからGUIでデプロイできる(Vercelのように)

コンソールからは1つのbackendしか作成できないが、apphosting:backends:createを実行すると2つ目のbackendが作成できる

firebase-framework-tools

現Cloud Functions版でアプリケーションを微調整する組込みライブラリのfirebase-framework-toolsはApp HostingではCloud Build内部処理に使われるようになっている。このためアプリケーションレベルでの変換はしていないはず

https://github.com/FirebaseExtended/firebase-framework-tools

この中の@apphosting系のパッケージがビルドの処理を実装している

Cloud Buildで接続されたGitHubリポジトリからソースコードを取得してフレームワークを自動で検知してCloud Runにデプロイする

例: フレームワーク検知部分を単体で実行する

❯ ./packages/@apphosting/discover/dist/bin/discover.js starters/nextjs/basic/
{
  "discovered": [
    {
      "framework": "nextjs",
      "version": "14.1.4",
      "packageManager": "npm",
      "platform": "nodejs"
    },
    {
      "framework": "react",
      "version": "18.2.0",
      "packageManager": "npm",
      "platform": "nodejs",
      "bundledWith": [
        "nextjs"
      ]
    }
  ]
}

リストは https://github.com/FirebaseExtended/firebase-framework-tools/blob/main/packages/%40apphosting/discover/src/index.ts にある

この結果を元にnpmレジストリの@apphosting/ - npm searchにマッチされ、専用モジュールでビルドされる

https://github.com/FirebaseExtended/firebase-framework-tools/blob/d193163ad778040a1e820652786d9b6fca61862a/packages/%40apphosting/build/src/bin/build.ts#L16-L31

Next.jsの場合の全体のビルドフローは

  1. @apphosting/discoverがdependenciesや設定ファイルの有無を元にframework検知する
    a. 新規作成する時は@apphosting/create
  2. frameworkごとに@apphosting/create、@apphosting/buildがある
    a. Next.jsは@apphosting/adapter-nextjs
  3. @apphosting/buildが.next/ディレクトリを解析して.apphosting/bundle.yamlを生成

bundle.yamlはこういうもの

bundle.yaml
headers: []
redirects: []
rewrites: []
runCommand: node .apphosting/server.js
neededDirs:
  - .apphosting
staticAssets:
  - .apphosting/public

.apphosting/に出力されたファイルをコンテナにして→Registry→Clood Runのimageにセットする

Google Cloud側の動作

以下のリポジトリのstarters/nextjs/basic/を自分の新規GitHubリポジトリにコピーしてデプロイする

https://github.com/FirebaseExtended/firebase-framework-tools/tree/main/starters/nextjs/basic

npm init @apphosting
❯ firebase apphosting:backends:create

i  === Import a GitHub repository
✔  Connected with GitHub successfully

firebase apphosting:backends:createするとGitHub認証が要求され接続するリポジトリを設定、そのままデプロイまでできる

どのリソースが使われているかは https://console.cloud.google.com/apis/dashboard から確認できる

観察していたところ以下が叩かれていた

Firebase App Hosting API
Developer Connect API
Compute Engine API
Cloud Logging API
Cloud Run Admin API
Secret Manager API
Cloud Pub/Sub API
Artifact Registry API
Identity and Access Management (IAM) API
Cloud Build API
Cloud DNS API

Cloud StorageにソースコードがチェックアウトされCloud Buildでビルドしたのちに、Container RegistryにイメージがpushされそれがCloud Runのイメージに設定される

デプロイが完了すると*.hosted.appというホストにアクセスできる

/isrでISRの動作を検証できる。

https://test.lai.so/isr/time

10秒感覚を空けて更新すると、その次のレスポンスでは内容が更新されているのでISRの挙動になっているのが分かる

このサーバーのレスポンスヘッダ

< alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
< alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
< content-type: text/html; charset=utf-8
< date: Wed, 15 May 2024 12:36:23 GMT
< etag: "133xe8xanos9vl"
< server: envoy
< vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Url, Accept-Encoding
< x-nextjs-cache: HIT
< x-powered-by: Next.js
< content-length: 12801
< via: 1.1 google
< cache-control: public,max-age=60,s-maxage=3600,stale-while-revalidate
< cdn-cache-status: miss

旧Cloud FunctionsにNext.jsをデプロイしたサーバーがあったので比較

< HTTP/2 200 
< cache-control: max-age=3600
< content-type: text/html; charset=utf-8
< etag: "4c77324b273ebe1c2d68279d9fcbea803d8bf98cb61b9e567efb33fc9b56421d"
< last-modified: Tue, 06 Sep 2022 13:00:34 GMT
< strict-transport-security: max-age=31556926; includeSubDomains; preload
< accept-ranges: bytes
< date: Wed, 15 May 2024 12:35:51 GMT
< x-served-by: cache-nrt-rjtf7700027-NRT
< x-cache: MISS
< x-cache-hits: 0
< x-timer: S1715776551.099519,VS0,VE117
< vary: x-fh-requested-host, accept-encoding
< alt-svc: h3=":443";ma=86400,h3-29=":443";ma=86400,h3-27=":443";ma=86400
< content-length: 8266

Cloud Run内のリソース設定を変更する時はapphosting.yamlを用意するとCloud Buildで適用してくれる

https://firebase.google.com/docs/app-hosting/configure

カスタムドメインはApp Hosting→Backend→Settingsから設計できる。Google Cloud側には設定がない。

Discussion