Firebase (v9) を利用してサクッと React アプリを作って公開する【支払い情報登録不要】
前書き
筆者はあまりクラウドサービスに詳しくなく、初心者による記事である。間違った記述があるかもしれないので指摘してくれるとありがたい。
おそらく他に良い構成があると思うが、今回は Firebase (v9) を使ってみた記録である。
またセキュリティについては一切考慮していない。
はじめて Firebase を触ってみる人に向けて記述しているつもりである。
対象読者
- フロントエンドだけでお金かけずアプリ作りたい人
- 文化祭とかのイベントでの一時的な公開などで、プロトタイプ等としてサクッと作りたい人
- 何らかの諸事情でどうしても支払い情報を登録したくない人
はじめに
「クラウドサービス便利だよね。サーバーレスだし。なんかかっこいいし。」
という気持ちで色々調べていくとさまざまなサービスがあり、その中にもいろんなプランや機能があって、調べるほどわからなくなる。無料枠があるといっても支払い方法登録しないと使えないとかもあったりで、そもそもクレジットカードとか持ってないという学生等もいるだろう。とりあえず何か作ってみたいし、できたら公開したいだけなのだ。
また、ただ「アプリ制作」に興味があるのであって、APIサーバーとかのバックエンドを一旦棚に上げておきたい。
そんなあなたに、 Firebase という選択肢
Google が提供しているクラウドサービス Firebase というものがある。(立ち位置的には BaaS だろうか。) Firebase の Spark プランであれば支払い方法登録なしで利用できる。あんまり支払い方法を登録したくない学生や個人にとってとてもありがたい。Firebase Hosting によるホスティングサービスも使えて、ドキュメント型 NoSQL のデータベースである Firestore も以下の範囲で利用できる。マイクロなものを作る分なら困らなそうだ。
Firestore 制限項目 | 制限内容 |
---|---|
保存済みのデータ | 合計1 GiB |
下り(外向き)ネットワーク |
10 GiB / 月 |
ドキュメントの書き込み |
2 万件 / 日 |
ドキュメントの読み取り |
5 万件 / 日 |
ドキュメントの削除 |
2 万件 / 日 |
(2024/2/2 時点)
Firebase で Web アプリを作ってみる
今回は入門として静的サイトのデプロイから始めて Firestore をほんのちょっとだけ使った「タイトル争奪戦」というしょうもない Web アプリを作ってみる。サイトの真ん中にある「タイトル」をただ書き換えられるだけのアプリである。
本記事ではこのアプリを TypeScript + React + Vite で構築する。ソースコードは GitHub で管理する。
静的サイトの手動デプロイまで一気に駆け抜ける
え、アプリって聞いたんですけど・・・?と思われるかもしれないが、まずは静的サイトを Firebase Hosting で公開するまでやる。これができればあとは配信するコンテンツを変えるだけである。
リポジトリの準備
まずは GitHub 上で、適当な名前で private リポジトリを作成する(以降このリポジトリ名をfirebase-test
とする)。そしてそれをローカル環境にgit clone
してくる。
ローカル環境の準備
開発環境として Node.js が必要だが、それを直接インストールするのではなく node のバージョン管理ツールであるvolta
を導入しておく。特に様々なプロジェクトをPC内に同居させているとバージョン管理が後々大変になるので導入しておくと良い。
React のプロジェクトの作成
TypeScript + React + Vite のプロジェクトを作成する。ありがたいことに Vite にはそれを作成する公式コマンドが用意されているのでそれを使う。
まず、clone
してきたfirebase-test
のフォルダにターミナルから入って、以下のコマンドを入力する。
npm create vite@latest . -- --template react-swc-ts
不慣れな場合はnpm create vite@latest
でインタラクティブに操作できる。ただしプロジェクト名を指定せずにカレントディレクトリ.
を指定する必要がある。
これができたら以下のコマンドで開発サーバーが立ち上がるか確認しよう。
npm install
npm run dev
そうすると http://localhost:5173 にアクセスすれば "Vite + React" と書かれたページが表示されるはずである。
ここまでを一旦commit
しておこう。
Firebase のプロジェクト作成
ここからは一旦ソースコードから離れて Firebase の Web サイト上で操作をする。まずトップページから [使ってみる] というボタンがデカデカとあるはずなのでそれを押す。すでに Google アカウントにログイン済みであれば、Google アカウントの認証画面をスキップしてコンソール画面が映し出されると思われる。Firebase が Google のサービスなので Google アカウントがあるとここらへんはややこしい手続きもなく相当スムーズに進める。
それでは [プロジェクトを作成] というボタンがあるのでそれを押す。
そのあとプロジェクト名やら Google アナリティクスの有効化やら聞かれる。今回の場合は Google アナリティクスについてはどっちでも良いが、どうせなら有効にしておいた方が良い。
最後に [プロジェクトを作成] のボタンを押してプロジェクトが作られる。
待機していれば以下の切り抜き部分を含む画面が現れるはずだ。
このページにあるようにアプリの追加を促してくる。今回は Web アプリなので真ん中の Web ボタンを押す。
アプリ登録画面に移動するので、そこでアプリのニックネームを入力する。"このアプリの Firebase Hosting も設定します。" というところをチェックする。するとURLの入力があるが、ここではデフォルト(おそらくプロジェクトID)を選択しておくのが無難である。あとは [アプリを登録] ボタンを押して設定完了である。それ以降は Firebase を利用する手順が書いてあるが、この記事で説明するのでスキップしていって構わない。
React プロジェクト上での Firebase 設定
これでようやく準備が整いつつあるが、ローカル環境側の準備をもう少ししなければならない。
ローカルの開発環境(firebase-test
フォルダ)に戻って、そこで以下のコマンドを実行する。
npm install firebase
npm install -g firebase-tools
firebase
の方はスクリプトから Firebase を操作するのに必要な物が入ったライブラリであり、firebase-tools
は Firebase の CLI ツールである。
次に以下のコマンドで Google アカウントのログインを行なう。
firebase login
ログインできたら firebase hosting 用の設定を以下のコマンドから行う。
firebase init hosting
さて、ここからは Firebase CLI からの質問攻めにあう。
? Please select an option: (Use arrow keys)
❯ Use an existing project
Create a new project
Add Firebase to an existing Google Cloud Platform project
Don't set up a default project
初回のみ上のような質問がされるが、すでに Firebase 上でプロジェクトを作っているのでUse an existing project
を選び、次にどのプロジェクトか選択するように言われるので、先ほど作ったプロジェクトを選ぶ。
? What do you want to use as your public directory?
ざっくりいうと今あるどのディレクトリを静的サイトのルートにしたいのかということだ。ここにはビルド済みファイルを入れる必要がある。ここはとりあえずdist
[1]と入力しよう。
? Configure as a single-page app (rewrite all urls to /index.html)?
yes
を解答しておこう。少なくとも今我々は React を使っていて single-page app (SPA) である。no
にしても動くが、例えば React Router とかを使おうとするとここらへんの恩恵を受ける。
? Set up automatic builds and deploys with GitHub?
とてもありがたいことにデプロイ用の GitHub Actions ワークフローを生成してくれる。のちのちここら辺を改めて操作するが、一旦手動デプロイのテストをしたいためno
にする。
(もちろん「手動デプロイ?知るかよ!」という人はここをyes
で回答してfirebase init hosting:github
の項目を先取りして見ると良い。)
そうすると.firebaserc
ファイルとfirebase.json
ファイルが生成されるはずだ。
ここらへんで設定を間違えたとしたら Vite の公式を参考にすれば大丈夫だし、再度firebase init hosting
すれば再設定できる。
ここまで終わったらcommit
して GitHub にpush
しておき、そしていよいよデプロイだ。
手動デプロイ
デプロイするためにはまず React アプリをビルドしないといけないので以下のコマンドを実行する。
npm run build
そうするとdist
フォルダが生成されたはずだ。あとは以下のコマンドでデプロイする。
firebase deploy
やったね!これでもうサイト自体は公開された。
Firebase コンソール上で Web アプリを追加する際 URL を設定するところがあったが、あそこがデフォルトであればhttps://{プロジェクトID}.web.app
にホスティングされているはずだ。
自動デプロイ
でも、毎回npm run build
してfirebase deploy
を手元でするの面倒だし、怖い!
そんなあなたに朗報、 Firebase はその自動化ワークフローを用意してくれている。それがさっき触れた? Set up automatic builds and deploys with GitHub?
をyes
と回答した時だ。すでにno
と回答していても以下のコマンドを実行すれば同じ挙動になる。
firebase init hosting:github
そうすると GitHub のログインや Firebase への権限の許可等をやるよう指示される。完了したら再びコマンドラインに戻ってこよう。ログインに成功したら以下の質問が出ているはずだ。
? For which GitHub repository would you like to set up a GitHub workflow? (format: user/repository)
これはデフォルト値が現在のリポジトリなのでそのまま Enter を押す。そうすると GitHub から デプロイできるように GitHub 側の設定をしてくれる。
? Set up the workflow to run a build script before every deploy?
この質問はデプロイワークフローにビルドを含むかどうか聞かれていて、Reactはビルド必須なのでyes
と回答する。
? What script should be run before every deploy?
そのあと、どんなスクリプト実行するか聞かれるが、デフォルトのnpm ci && npm run build
[2]で良いのでただ Enter を押す。
そうすると./github/workflows/firebase-hosting-pull-request.yml
が生成される。Pull Request (PR) 時にプレビュー用のサイトを用意するワークフローである。
多人数であればローカルにpull
してこなくてもサイトを確認できるのでめちゃくちゃ便利。完全に個人の開発であればいらないかもしれない。
? Set up automatic deployment to your site's live channel when a PR is merged?
PR をマージしたあと本番用にデプロイするか聞かれていて、yes
にしておく。
? What is the name of the GitHub branch associated with your site's live channel
どのブランチにマージされた時かと聞かれていて、デフォルトはmain
であり、今回もそれに従う。そうすると./github/workflows/firebase-hosting-merge.yml
が生成される。
実際に自動デプロイを動かしてみる
さてdev
ブランチを切って、App.tsx
の中身を適当に書き換える。
import "./App.css";
function App() {
return (
<>
<div>
<h1>Firebase、マジ神</h1>
</div>
</>
);
}
export default App;
これをcommit
&push
して GitHub から PR を作成する・・・前に、このままだと権限で PR 時のワークフローがうまくいかなかったりするので、少しだけ以下の記事に従って GitHub 上の設定を見直す。
GitHub のリポジトリの Settings→Actions→General の Workflow permissions で Read and Write Permissions を選択する。Allow Github Actions to create and approve pull requests も選択する。
これでdev
からmain
への PR を作成する。だがすぐにマージをしないでほしい。待っていると PR 時に GitHub Actions が走り、一時的なプレビューサイトを準備してその URL を貼り付けてくれる。レビュワー大歓喜である。
あとは PR をマージして少し待つと公開サイトの中身が切り替わっていることが確認できる。
これで自動デプロイのワークフローができた。
いよいよ Web アプリを作ってみる
静的サイトを Firebase Hosting にデプロイする話をしてきたが、実のところ Web アプリの話ではなかった。ここからようやく Web アプリの構築である。だが安心してほしい。ここまできたらあと1割と言っても良い。
というわけでここから「タイトル争奪戦」というアプリを作ってみる。
タイトル部分を書き換えるだけのなんの面白みもないアプリである。しかし Firestore というデータベースを操作するので立派な Web アプリである。
(この後の作業でワークフローの修正があるので、dev
ブランチを切ってやると良い。)
基本的な動作の実装
まずは Firestore との接続の前にuseState
を使って実装したい動きを作る。
import { useState } from "react";
import "./App.css";
function App() {
const [title, setTitle] = useState("Firebase、マジ神");
const [userTitle, setUserTitle] = useState("");
// ユーザー入力のハンドリング
function handleOnChangeUserTitle(newTitle: string) {
setUserTitle(newTitle);
}
// タイトルの変更
function handleOnSendTitle() {
setTitle(userTitle);
setUserTitle("");
}
return (
<>
<div>
タイトル争奪戦
<h1>{title}</h1>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
gap: 10,
}}
>
<input
type="text"
id="new-title"
name="new-title"
value={userTitle}
onChange={(e) => handleOnChangeUserTitle(e.target.value)}
/>
<input
type="button"
id="change-title"
name="change-title"
value="書き換える"
minLength={50}
onClick={() => handleOnSendTitle()}
/>
</div>
</div>
</>
);
}
export default App;
これで基本的な動きはできた。npm run dev
で確認しておくと今のままではタイトルの変更は別ブラウザ、別PCには共有されない。
Firestore の準備
Firestore を使う前に Firebase のコンソールで少し作業をする。左側のメニューから [構築] > [Firestore Database] を選ぶ。
すると以下のような Firestore のページに移動するので [データベースの作成] を押す。
そうするとデータベースのロケーションやセキュリティルールを設定する画面が出る。ロケーションは今回の場合はどこでも良いが、参考までに公式ドキュメントを見ておくと良い。セキュリティルールについては「テストモードで開始する」を選ぶと手っ取り早いが、後でちゃんと設定しておくのが良い。
これで Firestore が使える状態になった。
Firestore の導入
ここから Firestore と React アプリとを繋げる。
Firestore との接続にはいくつかの値が必要になる。まずは Firebase のプロジェクトトップから、プロジェクト名の下にある登録した Web アプリ をクリックする。
そうすると下の方に API key などが書かれたコードブロックがある。緑で塗られた位置にある値が Firestore (というよりもこのプロジェクトのリソース)に接続するために必要だ。このコードブロックはコピーできるが、この手の値は環境変数にしておいた方が良い。
よって、上の画像で塗りつぶした値を環境変数として登録する。なおリポジトリにこれらの環境変数があるのは望ましくないので、ファイル名を.env.local
にしておく。"<>"
の部分をそれぞれ対応する値に書きかえて使って欲しい。
VITE_FIREBASE_API_KEY="<apiKey>"
VITE_FIREBASE_AUTH_DOMAIN="<authDomain>"
VITE_FIREBASE_PROJECT_ID="<projectId>"
VITE_FIREBASE_STORAGE_BUCKET="<storageBucket>"
VITE_FIREBASE_MESSAGING_SENDER_ID="<messagingSenderId>"
VITE_FIREBASE_APP_ID="<appId>"
VITE_FIREBASE_MEASUREMENT_ID="<measurementId>"
そして 新たに src/firebaseApp.ts
ファイルを作り読み込み設定をする。
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";
const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID,
measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID
};
const app = initializeApp(firebaseConfig);
const database = getFirestore(app);
export { database };
そしてApp.tsx
を Firestore のデータベースとの更新部分を足して作り替える。
- import { useState } from "react";
+ import { useState, useEffect } from "react";
import "./App.css";
+ import { doc, setDoc, onSnapshot } from "firebase/firestore";
+ import { database } from "./firebaseApp";
+ const COLLECTION_NAME = "title"; // コレクション名
+ const UNIQUE_DATA_ID = "unique"; // ドキュメントID
// Firestore に格納されるデータの型定義
+ type Data = {
+ title: string;
+ };
// Firestore 上のタイトルデータを更新する
+ function updateData(newTitle: string) {
+ const dataDoc = doc(database, COLLECTION_NAME, UNIQUE_DATA_ID);
+ const data: Data = {
+ title: newTitle,
+ };
+ setDoc(dataDoc, data);
+ }
function App() {
const [title, setTitle] = useState("Firebase、マジ神");
const [userTitle, setUserTitle] = useState("");
// ドキュメントリスナーの生成
+ useEffect(() => {
+ const dataDoc = doc(database, COLLECTION_NAME, UNIQUE_DATA_ID);
+ const unsubscribe = onSnapshot(dataDoc, (snapshot) => {
+ if (snapshot.exists()) {
+ const data = snapshot.data() as Data;
+ setTitle(data.title);
+ }
+ return () => unsubscribe();
+ });
+ }, []);
// ユーザー入力のハンドリング
function handleOnChangeUserTitle(newTitle: string) {
setUserTitle(newTitle);
}
// タイトルの変更
function handleOnSendTitle() {
- setTitle(userTitle);
+ updateData(userTitle);
setUserTitle("");
}
return (
<>
<div>
タイトル争奪戦
<h1>{title}</h1>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
gap: 10,
}}
>
<input
type="text"
id="new-title"
name="new-title"
value={userTitle}
onChange={(e) => handleOnChangeUserTitle(e.target.value)}
/>
<input
type="button"
id="change-title"
name="change-title"
value="書き換える"
minLength={50}
onClick={() => handleOnSendTitle()}
/>
</div>
</div>
</>
);
}
export default App;
Firestore との連携部分の解説
今回は単一のドキュメントのみで制御しており、極めてシンプルな構成である。以下の三つの要素だけで成り立っている。
-
doc
DocumentReference
を生成する関数。DocumentReference
はドキュメントの住所情報だと思えば良い。doc
の引数はデータベース情報、コレクション名、ドキュメントIDが必要。
ドキュメントではなくCollectionReference
が必要であればcollection
がある。 -
setDoc
データをセットする関数。ドキュメントが存在しない場合は作成してくれる。更新も兼ねているが、実際にはupdateDoc
とちゃんと使い分けた方が良いと思われる。
また似たものでaddDoc
というものがある。setDoc
はDocumentReference
が第一引数でありドキュメントIDが必要で、被りがあれば上書きする。addDoc
はCollectionReference
が第一引数で、ドキュメント名を重複しない名前で自動採番し必ず新しいドキュメントを生成する。 -
onSnapshot
第一引数で指定されたドキュメントのデータ変更を監視し、更新を検知すると第二引数の関数を発火するリスナーを作ってくれる関数。返り値はリスナーを停止する関数。リアルタイムアップデートを実現する今回の要。
`useEffect`について
特にドキュメントリスナーの生成は(コンポーネント内部の値に依存しない限り)最初の一回限りで良い。これを実現するのが以下の形である。
useEffect(() => {
// set up
return () => {
// clean up
}
}, [])
useEffect
の第二引数は依存配列と呼ばれ、この依存配列の中身に変更があれば第一引数の関数が発火する仕組みである。空の配列[]
を指定することで最初の一回以降変更が起こることがないため、最初の一回だけ呼ばれるようになる。ちなみにuseMemo
と混同されがちだが、React の公式説明を見れば位置付けが全く異なるので注意すること。
正確で詳しい情報は React の公式ドキュメントを参照するに越したことはない。
npm run dev
で開発サーバーを立ち上げ、二つのブラウザウィンドウを起動。そして片方のブラウザからタイトルを変更した時に、もう一つのタイトルが変更されれば成功である。
Firestore のデータベースの中身を確認すると、title
コレクションのunique
というデータがちゃんと入っているのが確認できる。
いざデプロイ! のはずが
さて、これであとは PR してmain
ブランチにマージすれば.github/workflows/firebase-hosting-merge.yml
のワークフローが動いてデプロイしてくれる・・・と言いたいところだが、このままでは環境変数が解決できずうまくいかない。
試しに手元で.env.local
のファイル名を.env.ignore
に書き換えてnpm run build && npm run preview
を実行してみると良い。一見正しく動いているように見えるが、Firestore のデータが参照されておらずタイトルの書き換えが反映されない。実際検証ツールでみるとエラーが出ており Firestore にアクセスできていない。
先ほど書き換えた.env.ignore
を.env.local
に戻して、再度npm run build && npm run preview
してみよう。すると期待通り動くことがわかる。
さて、重要なのは.env.local
に定義された環境変数がビルド時に存在するかどうかだ。
しかし、今 GitHub 上にこのファイルは存在しない。一つの解決策として.env.local
を.env
にファイル名を変えれば Git の管理対象になるので GitHub 上に置くことができ、問題なくビルドできるようになるが、それは避けたい。
GitHub secrets の登録とワークフローの修正
ということで、ワークフローの修正と GitHub 上の操作を通してこれを解決したい。
まずは GitHub 上での操作を行う。要は GitHub 上のどこかにそれがあれば良いのだ。
GitHub にはsecrets
という機能がある。
シークレットは、組織、リポジトリ、またはリポジトリ環境内に作成する変数です。作成したシークレットは、GitHub Actionsワークフローで利用できます。 GitHub Actions でシークレットを読み取ることができるのは、シークレットをワークフローに明示的に含める場合のみです。
つまりワークフロー上で参照できるが、一度登録すれば簡単には中身が見えないようになる機能だ。
GitHub の「リポジトリのシークレットの作成」の項を参照しながら.env.local
に書いた環境変数を一つずつ加えていく。(secretsの登録名として冗長なVITE_
部分は消しても良いが、この後のワークフローの修正時はsecrets.
以下を自身が設定したものに読み替えて修正してほしい。)
さて、あとはワークフローからこれを参照すれば良いが、必要なステップはnpm ci && npm run build
のところなので、そこにenv:
を利用して環境変数を流す。
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools
name: Deploy to Firebase Hosting on merge
on:
push:
branches:
- main
workflow_dispatch:
jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
+ env:
+ VITE_FIREBASE_API_KEY: ${{ secrets.VITE_FIREBASE_API_KEY }}
+ VITE_FIREBASE_AUTH_DOMAIN: ${{ secrets.VITE_FIREBASE_AUTH_DOMAIN }}
+ VITE_FIREBASE_PROJECT_ID: ${{ secrets.VITE_FIREBASE_PROJECT_ID }}
+ VITE_FIREBASE_STORAGE_BUCKET: ${{ secrets.VITE_FIREBASE_STORAGE_BUCKET }}
+ VITE_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.VITE_FIREBASE_MESSAGING_SENDER_ID }}
+ VITE_FIREBASE_APP_ID: ${{ secrets.VITE_FIREBASE_APP_ID }}
+ VITE_FIREBASE_MEASUREMENT_ID: ${{ secrets.VITE_FIREBASE_MEASUREMENT_ID }}
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: "${{ secrets.GITHUB_TOKEN }}"
firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_(your-project-id) }}"
channelId: live
projectId: (your-project-id)
.github/workflows/firebase-hosting-pull-request.yml
が残っているのであれば、そちらの方も忘れずに修正しよう。
あとはmain
ブランチにpush
すれば、ワークフローがトリガーされて自動的にデプロイまでしてくれる。
実際にホスティングされている URL にアクセスすれば想定通り動いてくれることがわかるだろう。
おめでとう! これで Firebase を利用した Web アプリが公開できた!
Firestore というデータベースを利用した正真正銘の動的サイトであり、しかも公開もできるようになった。
少なくともここまでの内容を知っていれば大体の小規模アプリは作れるようになるはずだ。
(おまけ) Firebase Hosting を停止する方法
ローカルの開発環境で良いのでfirebase hosting:disable
を実行する。すると
? Are you sure you want to disable Firebase Hosting for the site プロジェクト名
This will immediately make your site inaccessible! (Y/n)
と聞かれるので、yes
と回答すると停止できる。
Discussion