要点整理!Next.js を Firebase にデプロイする方法

こんにちは。 最近盆栽になるであろう芽に水をあげるのが日課の id:hokupod です。

Web フロントエンド界隈に手を出すのは今じゃないと心の中で言い続けてきました。。 しかし React の勢いが留まることを知らないため、社内ツールから Next.js をはじめて見ようと決心した際の記録です。 (決心したら、今度は Astro で MPA いいよね!という声も出て来て、本当安定しない界隈だなと感じました。発展し続けているのはスゴイこと)

ということで、今回は Next.js を使用して Firebase にデプロイする際に詰まったこと、得た学びについて書きたいと思います。 実際にどんな社内ツールを作ったのかというと、シンプルな管理画面付きの短縮 URL サービスです。

短縮URL生成してくれる君

動機

そもそもなぜ Next.js と Firebase の組み合わせで開発をしたのかについて、触れていこうと思います。 まず Next.js については、下記が選定理由です。

  1. 触りたかったReact が使用されていること。
  2. フルスタックであり、バックエンド処理も記述することが出来るため、多少だがバックエンド処理を別のフレームワークにするより学習コストが低い
  3. Remix など他フレームワークと比べるとシェアが高いように思える(要出典)

Firebase については、下記が選定理由です。

  1. Google ワークスペースを利用した認証がおこなえる
  2. 安い
  3. サーバ持ちたくない
  4. 大抵の物は揃っている
  5. Next.js をどうやらデプロイできるらしい

両方とも大した理由ではないと言われてしまいそうですねw 今回は社内ツールということもあり、長生きしそうか・安価か・社内からのアクセスに限定ができるか・チャレンジ要素はあるかを特に重視しました。

当社は、AWS パートナーネットワーク(APN)を取得していることもあり、普段は AWS を使用した開発が主です。 しかし前述の通り、チャレンジ要素としての技術調査で Firebase を使用した形です。

技術スタック

技術スタックとしては、ざっくりですが下記の通りになります。

短縮 URL を登録する管理画面、短縮URLからリダイレクトする処理はそれぞれ別にリポジトリを用意して管理しています。

ではここから実際に Next.js のプロジェクトを Firebase にデプロイする流れを追っていきます。

パッケージマネージャーとして、yarn を使用しますので、npm を使用されている方は適宜読み替えください。 また事前に Firebase プロジェクトは作成済みであるとします。

デプロイの流れ

今回使用した構成は、下記のとおりです。

ファイル名に付与している絵文字の説明 🆕 Firebase を使用するために新規追加したファイル ✍ Firebase を使用するために編集したファイル

.
├── .firebaserc
├── dprint.json
├── 🆕 firebase.json
├── 🆕 firebaseFunctions.js
├── next.config.js
├── node_modules
├── ✍ package.json
├── postcss.config.js
├── src
│   ├── components
│   ├── contexts
│   ├── libs
│   ├── pages
│   ├── public
│   ├── styles
│   └── types
├── tailwind.config.js
├── tsconfig.json
└── yarn.lock

ライブラリのインストール

Firebase を使用するためのライブラリをインストールします。 下記のうち firebase-tools はデプロイなど Firebase の操作に使用するライブラリになります。

yarn add firebase-functions
yarn add -D firebase-tools

必要ファイルの追加と編集

前述の構成にもマークを付けておりますが、下記 4 ファイルを Firebase にデプロイする上で追加・編集します。

新規追加

  1. .firebaserc
    • Firebase Hosting で使用するリソースファイル
  2. firebase.json
    • Firebase 用の設定ファイル。今回は Firebase Hosting / Cloud Functions for Firebase の設定を記載する
  3. firebaseFunctions.js(ファイル名は任意)
    • Cloud Functions for Firebase にデプロイされる関数。Next.js のバックエンド処理に委譲する

編集

  1. package.json
    • Cloud Functions for Firebase のエントリポイントの設定、Node のバージョン指定、ビルドタスクをまとめるために使用

ではここから一つ一つのファイルの詳細を見ていきます。

.firebaserc

使用するプロジェクト名を記載します。

{
  "projects": {
    "default": "firebase-project-name"
  }
}

firebase.json

重要な点は、下記2点になります。

  1. Next.js で Firebase Hosting に配置する静的ファイルを生成するコマンド next export の吐き出し先は out になるため、hosting.public で指定する
  2. Node のバージョンを合わせるため、functions.runtime を指定する
{
  "hosting": {
    "public": "out",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      {
        "source": "**",
        "function": "nextjsFunc"
      }
    ]
  },
  "functions": {
    "source": ".",
    "runtime": "nodejs16"
  }
}

firebaseFunctions.js(ファイル名は package.json で指定するため任意)

Next.js のバックエンド処理部分のエクスポートである next build の吐き出し先を、下記ソースの nextjsDistDir に指定する必要があります。 デフォルトは .next ですが、もし next.config.js にて distDir: 'your-dist-dir-path' と指定している場合は、next.distDir を使用することも可能です。

Cloud Functions for Firebase への HTTP リクエストをすべて Next.js のサーバサイドに委譲する形を取っています。

const { https } = require('firebase-functions')
const { default: next } = require('next')

const nextjsDistDir = '.next'

const nextjsServer = next({
  dev: false,
  conf: {
    distDir: nextjsDistDir,
  },
})
const nextjsHandle = nextjsServer.getRequestHandler()

exports.nextjsFunc = https.onRequest((req, res) => {
  return nextjsServer.prepare().then(() => nextjsHandle(req, res))
})

package.json

package.json には、ビルドタスクを設定するだけではなく、Cloud Functions for Firebase の設定も記載します。

  1. main には関数を記載したファイルを指定します。
  2. engines.node には関数で使用する Node.js のバージョンを指定します。

scripts へは login, serve, shell, deploy, logs を追加をしています。

  1. scripts.login は、デプロイ前などに CLI から Firebase にログインするために使用
  2. scripts.serve は、開発用サーバである Firebase のエミュレータ起動用
  3. scripts.shell は、Cloud Function for Firebase のローカル起動用(使ってない
  4. scripts.deploy は、本番環境へのデプロイ用
  5. scripts.logs は、デプロイ後の Cloud Function for Firebase のログを見る用
{
  "private": true,
  "main": "firebaseFunctions.js",
  "engines": {
    "node": "16.x"
  },
  "scripts": {
    ...
    "login": "firebase login",
    "serve": "next build && next export && firebase emulators:start --only functions,hosting",
    "shell": "next build && firebase functions:shell",
    "deploy": "next build && next export && firebase deploy --only functions,hosting",
    "logs": "firebase functions:log",
    ...
  },
}

以上でファイルの追加・編集は完了です。 続いて、デプロイの実行です。

デプロイの実行

デプロイ前に注意事項です。 Cloud Functions for Firebase を利用するためには、Blaze プランにする必要があります。 つまりは、無料枠だけを使うという安心感は手放す必要があるということです。。

とはいえ、無料枠は十分にありますので、すぐさまお金が多くかかるという事態には陥らないはずです。 (もちろん、セキュリティには注意が必要です。)

Blaze プランに変更が完了したら、あとは簡単です。下記を実行するとデプロイされます。

yarn login
yarn deploy

これだけで実は Next.js から見ている環境変数ファイル .env.local も参照されている状態でデプロイされます。 私はこれだけでちょっと感動しました。 だって環境変数周りって、毎回ゴニョゴニョするじゃないですか。。

まとめ

こうまとめてみると、意外と簡単に(?)Firebase 環境に Next.js をデプロイできることがわかります。 Firebase さまさまです。 社内ツールでは、必ずなんらかの認証を挟まないといけないという制約が生まれます。 Firebase では Google Workspace での認証、Azure では Azure Active Directory での認証など、ツールのためのアカウントを作成するといった利用者の手間を省ける仕組みは積極的に利用したいと思いました。

Next.js の話にはなりますが、フロント・バックの両方を JS で一貫して書けるというのは、頭の切り替えが最小で済むというメリットがありました。 React はりあクト!で学びましたが、歴史含め体系的にまとまっており、フロントエンド初心者の私でもやり切れる知識を付けることができたので大変オススメです。

klemiwary.com

また今回 Copilot (with Vim) を使用してコーディングをしたのですが、フレームワークに乗っかるための書き方・ライブラリの使い方は Copilot が教えてくれて面白かったです。 もちろん思っていたのと違う形に補完されてしまったりするので、レビューはちゃんとする必要がありますが、さささっとプロトタイピングするには向いていると思いました。 ライセンス問題など、まだまだ課題があるようですが、解決されて気持ちよく使える日がくることを心待ちにしています。

最後になりますが、今回のチャレンジをするキッカケをくださった人事および情シスのみなさま、実装に際してアドバイスをくださったエンジニア・デザイナーのみなさま、そして長文にも関わらず最後までお読みいただいたみなさま、大変ありがとうございました。