この記事は何
Webアプリ初心者が育児ダッシュボードを作るために頑張っています。
勉強したことや作っていく中でつまづいたことを書いていきます。
この記事で学べること
今回はReact Router v7を使ってSPAを構築、Firebaseにデプロイします。
npm create vite@latestからReact Router v7を使ったアプリを構築するのですが、
大量にファイルが作られて、初心者には大変理解が難しいと思います。
そこら辺も分かるように調べながらまとめました。
- React Router v7を使ったプロジェクトの初期構築およびファイルの構成
- Firebaseでのプロジェクト初期構築およびファイルの構成
- Firebaseへのデプロイ
- GitHubActionsからのFirebaseデプロイ(簡易手順と応用手順)
ポイント
-
npm create react-appは非推奨。推奨はnpm create vite@latest -
React Router v7を使うと、一般的なviteアプリと異なりindex.htmlが生成されない - firebaseにSPAとしてデプロイするには、次を合わせる必要がある
-
firebase.jsonのhosting.publicをbuild/clientにする -
npm run build時のビルド先フォルダ名(vite.config.tsで設定可能。デフォルトはbuildなのでそのままでOK) -
react-router.config.tsでssr: falseにしてindex.htmlがbuild/clientに生成されるようにする
-
- Firebaseには、『プレビュー』と言う便利な機能がある
- GitHubActionsを使ってデプロイする場合、OIDCによる認証が推奨
大まかな手順
- Firebaseプロジェクトの作成
- Gitリポジトリの作成
- viteで初期構築
- Firebase設定
- 手動でFirebaseにデプロイ
- GitHubActionsの設定
前提
node.jsとfirebaseのインストールをやっておきましょう。
GCPアカウントがない場合は作っておきましょう。
# node.jsのインストール(Macの場合)
brew install node
# firebase-toolsのインストール
npm install -g firebase-tools
# firebaseにログイン。ブラウザが起動します
firebase login
(手順1) Firebaseプロジェクトの作成
検証環境用と商用環境用にそれぞれプロジェクトを作成。
本当なら、商用、ステージング、開発環境で用意したいところですが、
Firebaseではプロジェクト数に20の上限があるらしいこと、テストはローカルでできるらしいことから、商用と開発環境だけ用意します。
GeminiとAnalyticsは、やって損がなさそうなので有効化しました。

(手順2) Gitリポジトリの準備
こちらの記事に沿って、SSH鍵登録しておけば、下記コマンドでcloneできます。
GitHubでssh接続する手順~公開鍵・秘密鍵の生成から~
$ git clone ssh://git@github.com/<アカウント名>/<リポジトリ名>
Cloning into '<リポジトリ名>'...
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (4/4), done.
(手順3)Viteでアプリ作成
Viteで初期構築します。
create-react-appは非推奨なんだそうですね。
cloneしたフォルダの上でnpm create vite@latestをします。
$ ls
<リポジトリ名> #cloneしたプロジェクトフォルダの上
$ npm create vite@latest
Project nameを聞かれます。
入力した値のフォルダが作成されてしまうので、clone済みフォルダを入力。
既存ファイルをどうするか聞かれるので、既存ファイルは削除せずそのまま続けてもらいます。
◇ Project name:
│ <clone済みフォルダ名>
◇ Target directory "<リポジトリ名>" is not empty. Please choose how to proceed:
│ Ignore files and continue
◇ Select a framework:
│ React
次でオプションを選択可能です。せっかくなのでReact Routerを選択してSPAで複数ページ作れるようにしました。他に目ぼしい選択肢は次の通りです。
- TypeScript
- TypeScript+SWC
- コンパイルに SWC (Speedy Web Compiler) を使用することでコンパイルが劇的に高速化
- TypeScriptを使用し、かつ開発時のコンパイル速度を最優先したい場合に最適
- React Router v7
- React でルーティングを実現するための標準的なライブラリ、React Router を使用する
- 選択するケース
- ViteとReactを使って、複数のページやビューを持つアプリケーションを開発する場合
- SPAとして、URLの変更に応じてUIを切り替えたい場合
◇ Select a variant:
│ React Router v7 ↗
> npx
> create-react-router <リポジトリ名>
create-react-router v7.6.1
◼ Directory: Using <リポジトリ名> as project directory
◼ Using default template See https://github.com/remix-run/react-router-templates for more
✔ Template copied
何かのテンプレートをコピーしたっぽい後、ファイルを上書きしていいか聞かれるのでYes
overwrite Your project directory contains files that will be overwritten by
this template (you can force with `--overwrite`)
Files that would be overwritten:
README.md
Do you wish to continue?
Yes
◼ Nice! Git has already been initialized
npm installを聞かれるのでYes
deps Install dependencies with npm?
Yes
✔ Dependencies installed
done That's it!
Enter your project directory using cd ./<リポジトリ名>
Check out README.md for development and deploy instructions.
Join the community at https://rmx.as/discord
ここまできたら、
$ cd ./<cloneしたフォルダ名>
$ npm run dev
> dev
> react-router dev
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
http://localhost:5173/ にアクセスすると見えます。

npm create vite@latestでできたファイルの解説
-
ポイント1: 一般的なviteアプリにはある
index.html(JavaScriptのエントリポイント)がない。(root.tsxが相当しているようですが詳しくはわからないのでこちらを参照してください:React Router v7 ドキュメント 日本語版) - ポイント2: ルーティングはroutes.tsに定義する。home.tsxにルーティングされるようになっていてそこにwelcome.tsxがマウントされている
<cloneしたフォルダ>
├── .git
│ └── 略
├── .gitignore
├── README.md
├── app
│ ├── app.css
│ ├── root.tsx #viteアプリでいうindex.html相当
│ ├── routes.ts #ルーティングをここに定義。home.tsxが定義されている。
│ ├── routes
│ │ └── home.tsx #welcome.tsxがマウントされている
│ └── welcome
│ └── welcome.tsx #トップ画面
├── public
│ └── favicon.ico #react-routerのアイコン
├── Dockerfile #補足参照
├── .dockerignore
├── react-router.config.ts #react-routerの設定。ssr(Server-side Rendering)の有効/無効化など
├── tsconfig.json #typescriptのコンパイル設定
├── vite.config.ts #viteの設定
├── package.json #何を(どのパッケージを)インストールするか、そのバージョン範囲はどうか、という「意図」を記述。
| #npm installすると条件を満たす最新版をinstall
├── package-lock.json #package.jsonの意図に基づいて実際に何がインストールされたか、その「結果」を記述。
| #npm ci するとこちらをinstall
└── node_modules #インストールしたパッケージ本体
└── 略
(補足)Dockerfileについて
npm run devではなく、Docker上で実行することもできるようです。
PC環境の影響を受けない、より本番に近い環境で動作確認できる、といったメリットがありそうです。
# イメージのビルド
docker build -t myapp:latest .
# ローカルの3000番をdockerの3000に接続
docker run -p 3000:3000 myapp:latest
# http://localhost:3000 にアクセス
(手順4)Firebase設定
次にWebAppをFirebaseにデプロイできるよう設定していきます。
cd <cloneしたフォルダ>
firebase init #cloneフォルダ内で実行する
なんのサービスを使うか聞かれます。それぞれはこんな感じだそうです。
- Data Connect: RDB
- Firestore: NoSQLデータベース
- Genkit: AI搭載アプリケーションを構築
- Functions: API
- App Hosting: より複雑なウェブアプリケーション、特にサーバーサイドのロジックやAPIを必要とするモダンなフレームワーク(Next.jsのAPIルートなど)のホスティング
- Hosting: 主に静的コンテンツやシンプルなシングルページアプリケーション(SPA)
- Storage: ユーザーが生成したコンテンツを保存するストレージサービス
- Emulators: Firebaseの主要なサービス (Authentication, Firestore, Realtime Database, Cloud Functions, Hosting, Pub/Sub, Storage) をローカル環境でエミュレート
- Remote Config: アプリのUI変更、機能のON/OFF切り替え、A/Bテストなどを、アプリのバージョンアップなしで行える
- Extensions: 画像のリサイズ、メール送信、決済処理など、定型的なサーバーサイド処理を簡単に導入
- Realtime Database: 初期のNoSQLデータベース。Firestoreよりもシンプル
今回は、シングルページアプリケーション+APIのWebアプリを作るので、Functions, Hosting, Emulatorを選択。
スペースで選択して、Enterします。
後からでも、firebase initから追加可能です。
$ cd <cloneしたフォルダ>
$ firebase init #cloneしたフォルダの中で実行する
######## #### ######## ######## ######## ### ###### ########
## ## ## ## ## ## ## ## ## ## ##
###### ## ######## ###### ######## ######### ###### ######
## ## ## ## ## ## ## ## ## ## ##
## #### ## ## ######## ######## ## ## ###### ########
Youre about to initialize a Firebase project in this directory:
/Users/<Path>/<Cloneフォルダ名>
? Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter to confirm your
choices.
# なにをセットアップするか聞かれる。スペースで洗濯してEnter
◯ Genkit: Setup a new Genkit project with Firebase
◉ Functions: Configure a Cloud Functions directory and its files
◯ App Hosting: Configure an apphosting.yaml file for App Hosting
❯◉ Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys
◯ Storage: Configure a security rules file for Cloud Storage
◉ Emulators: Set up local emulators for Firebase products
◯ Remote Config: Configure a template file for Remote Config
Firebase init ~Fucntionsの設定~
対話形式で作成するプロジェクトの情報を入れていきます。
先ほど作成した、Firebaseの開発用プロジェクトに設定しておきます。
オプションなしでfirebase deployすると、開発用プロジェクトにデプロイされます。
✔ Please select an option: Use an existing project #既存プロジェクトを利用
✔ Select a default Firebase project for this directory: dev-... #開発用プロジェクト
i Using project dev-... (dev-...)
言語やESLintの厳しさを回答して、packageをインストール。
=== Functions Setup
Lets create a new codebase for your functions.
A directory corresponding to the codebase will be created in your project
with sample code pre-configured.
See https://firebase.google.com/docs/functions/organize-functions for
more information on organizing your functions using codebases.
Functions can be deployed with firebase deploy.
# 言語はTypeScriptを選択
✔ What language would you like to use to write Cloud Functions? TypeScript
# ESlintするを選択→functions配下にいくつかのファイルが生成されます
✔ Do you want to use ESLint to catch probable bugs and enforce style? Yes
✔ Wrote functions/package.json
✔ Wrote functions/.eslintrc.js
✔ Wrote functions/tsconfig.dev.json
✔ Wrote functions/tsconfig.json
✔ Wrote functions/src/index.ts
✔ Wrote functions/.gitignore
# packageインストール
✔ Do you want to install dependencies with npm now? Yes
...
以下のfirebase.jsonが生成される。
{
"functions": [
{
"source": "functions", //functionsフォルダ配下をコンパイル
"codebase": "default",
"ignore": [
"node_modules",
...
],
"predeploy": [ //deploy前にこのコマンドが実行される。
"npm --prefix \"$RESOURCE_DIR\" run lint",
"npm --prefix \"$RESOURCE_DIR\" run build"
]
}
],
...
}
Firebase init ~Hostingの設定~
続いてHosting設定。ここは少し注意。
デプロイするindex.htmlの格納フォルダを指定する。
プロジェクトによるがreact-routerを使い、CSRの場合はbuild/client配下にindex.htmlが生成される。なのでbuild/clientを入力。
=== Hosting Setup
# ⚠️注意。よくわからなければpublicのままにしましょう。後からfirebase.jsonで変更可能。
✔ What do you want to use as your public directory? build/client
# SPAを作ります。
✔ Configure as a single-page app (rewrite all urls to /index.html)? Yes
GitHubも設定。YesするとGitHubが開いてログインします。
✔ Set up automatic builds and deploys with GitHub? Yes
i dist/index.html is unchanged
i Detected a .git folder at /Users/<Path>
i Authorizing with GitHub to upload your service account to a GitHub repository's secrets store.
Visit this URL on this device to log in:
https://github.com/login/oauth/authorize?client_id=...
Waiting for authentication...
✔ Success! Logged into GitHub as <GitHubユーザ名>
次に、username/repositoryname を入力。
これによって、次が行われて、デプロイできるようになる仕組み。
- GCPプロジェクト内に、GitHub用のサービスアカウントを作成
- GitHub上のsecretsに、サービスアカウントのキーを設定
firebaseには『プレビュー機能』があり、プレビュー用チャネルにデプロイできる。
mainへのプルリク時にプレビューできるようにデプロイできる。
✔ For which GitHub repository would you like to set up a GitHub workflow? (format: user/repository) <GitHubユーザ名>/<リポジトリ名>
# デプロイ用サービスアカウントをGCPに作成
✔ Created service account github-action-xxxxxxx with Firebase Hosting admin permissions.
# サービスアカウントのキー(json)GitHub Secretsに保存
✔ Uploaded service account JSON to GitHub as secret FIREBASE_SERVICE_ACCOUNT_<PROJECT名>.
i You can manage your secrets at https://github.com/<GitHubユーザ名>/<リポジトリ名>/settings/secrets.
✔ Set up the workflow to run a build script before every deploy? Yes
✔ What script should be run before every deploy? npm ci && npm run build
# プレビューデプロイ用のGitHubActionsが生成される。
✔ Created workflow file /Users/<Path>/.github/workflows/firebase-hosting-pull-request.yml
✔ Set up automatic deployment to your sites live channel when a PR is merged? Yes
✔ What is the name of the GitHub branch associated with your sites live channel? main
✔ Created workflow file /Users/<Path>/.github/workflows/firebase-hosting-merge.yml
i Action required: Visit this URL to revoke authorization for the Firebase CLI GitHub OAuth App:
https://github.com/settings/connections/applications/xxxxx
i Action required: Push any new workflow file(s) to your repo
以下の設定が生成される
{
...
"hosting": {
"public": "build/client", // 入力したフォルダ設定
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [ // SPAにしたのでindex.htmlをデプロイするようになる
{
"source": "**",
"destination": "/index.html"
}
]
},
...
}
Firebase init ~Emulatorsの設定~
FunctionsとHostingを設定しておきます。
ログイン機能をつける場合はAuthenticationもやってもいいかもしれません。
=== Emulators Setup
? Which Firebase emulators do you want to set up? Press Space to select emulators, then Enter to confirm your choices.
◯ App Hosting Emulator
◯ Authentication Emulator
◉ Functions Emulator
❯◯ Firestore Emulator
◯ Database Emulator
◉ Hosting Emulator
◯ Pub/Sub Emulator
その後portを聞かれるので適当にOKしていきます。
✔ Which port do you want to use for the functions emulator? 5001
✔ Which port do you want to use for the hosting emulator? 5000
✔ Would you like to enable the Emulator UI? Yes
✔ Which port do you want to use for the Emulator UI (leave empty to use any available port)? 5002
✔ Would you like to download the emulators now? Yes
i ui: downloading ui-v1.15.0.zip...
✔ Wrote configuration info to firebase.json
✔ Wrote project information to .firebaserc
✔ Firebase initialization complete!
これで完了です!
Firebase init で生成されたファイル
以下のファイルが生成されました。
<リポジトリ名>/
├── .firebaserc #どのFirebaseプロジェクトにリンクされているか。通常、手動で編集しない
├── firebase.json #Hostingの公開ディレクトリ、URLリライトルール、Functionsのソースディレクトリやランタイム、Emulatorsのポート設定など
└── functions/
├── .eslintrc.js #ESLintの設定ファイル。コードの品質と一貫性を保つためのルールを定義
├── package.json Functionsが依存するnpmパッケージ
├── tsconfig.json #TypeScriptのコンパイル設定ファイル
├── tsconfig.dev.json
└── src/
└── index.ts #Functionsの関数
(参考)Functionsに関する補足
Functionsを複数作る場合は、こんな感じにする。
functions/
└── src/
├── index.ts # メインのエントリーポイント
├── a.ts
├── b.ts
└── utils/ # 共通ユーティリティ
└── helpers.ts
import * as functions from 'firebase-functions';
import { aFunction1, aFunction2 } from './a'; // a.tsから関数をインポート
import { bFunction1, bFunction2 } from './b'; // b.tsから関数をインポート
// a.tsで定義された関数をエクスポート
export const aFunc1= functions.https.onCall(aFunction1);
export const aFunc1= functions.https.onCall(aFunction2);
// b.tsで定義された関数をエクスポート
export const bFunc1= functions.https.onCall(bFunction1);
export const bFunc1= functions.https.onCall(bFunction2);
// 必要に応じて、他の共通関数などもここでエクスポート
// export { someUtilityFunction } from './utils/helpers';
開発環境で確認してみる
プロジェクトのルートで、npm run dev実行。
http://localhost:5173/ にアクセスするとサンプルが確認できる。

(手順5)ビルド〜手動デプロイ
ここまで来れば、firebase deploy でデプロイできるはずだが、エラーになる。
$ firebase deploy
=== Deploying to '<開発用Firebaseプロジェクト>'...
i deploying functions, hosting
Running command: npm --prefix "$RESOURCE_DIR" run lint
> lint
> eslint --ext .js,.ts .
=============
WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree.
You may find that it works just fine, or you may not.
SUPPORTED TYPESCRIPT VERSIONS: >=3.3.1 <5.2.0
YOUR TYPESCRIPT VERSION: 5.8.3
Please only submit bug reports when using the officially supported version.
=============
/Users/<Path>/functions/src/index.ts
11:9 warning 'onRequest' is defined but never used @typescript-eslint/no-unused-vars
12:13 warning 'logger' is defined but never used @typescript-eslint/no-unused-vars
27:19 error There should be no space after '{' object-curly-spacing
27:36 error There should be no space before '}' object-curly-spacing
✖ 4 problems (2 errors, 2 warnings)
2 errors and 0 warnings potentially fixable with the `--fix` option.
Error: functions predeploy error: Command terminated with non-zero exit code 1
Having trouble? Try firebase [command] --help
実は、生成されるfunctionsが、lintで引っかかっている。
index.tsを修正するか、lintを一時的に無効化する。無効化する場合は以下を修正。
{
"functions": [
{
"source": "functions",
"codebase": ...
"predeploy": [
- "npm --prefix \"$RESOURCE_DIR\" run lint" ★ここを削除
"npm --prefix \"$RESOURCE_DIR\" run build"
]
}
],
もう一つ、修正が必要。
firebaseはSPAのつもりだが、react-routerがSPAになっていないことでdeployすると期待の表示にならない。
import type { Config } from "@react-router/dev/config";
export default {
// Config options...
// Server-side render by default, to enable SPA mode set this to `false`
- ssr: true,
+ ssr: false, // Server-side Renderingを無効化
} satisfies Config;
こうすることで、npm run buildでbuild/client/index.htmlが生成される。
firebase.jsonには"public": "build/client"を設定しているのでこれでデプロイができそうだけどもう一歩。
$ firebase deploy
=== Deploying to '<Firebaseプロジェクト名>'...
i deploying functions, hosting
Running command: npm --prefix "$RESOURCE_DIR" run build
> build
> tsc
✔ functions: Finished running predeploy script.
i functions: preparing codebase default for deployment
i functions: ensuring required API cloudfunctions.googleapis.com is enabled...
i functions: ensuring required API cloudbuild.googleapis.com is enabled...
i artifactregistry: ensuring required API artifactregistry.googleapis.com is enabled...
⚠ functions: missing required API cloudbuild.googleapis.com. Enabling now...
⚠ artifactregistry: missing required API artifactregistry.googleapis.com. Enabling now...
Error: Your project testtest-a78 must be on the Blaze (pay-as-you-go) plan to complete this command. Required API cloudbuild.googleapis.com can't be enabled until the upgrade is complete. To upgrade, visit the following URL:
https://console.firebase.google.com/project/testtest-a78/usage/details
firebaseのプランを変えろと言われているので変更します。
Functionsを使うには、Blazeプランにする必要があります。Firebaseのコンソールから変更したら再度firebase deploy!
なお、--forceをつけています。これをつければ、functionsをデプロイした時に生成されるファイルをすべてクリーンアップしてデプロイしてくれます。
$ firebase deploy --force
=== Deploying to '<Firebaseプロジェクト名>'...
i deploying functions, hosting
Running command: npm --prefix "$RESOURCE_DIR" run build
> build
> tsc
✔ functions: Finished running predeploy script.
i functions: preparing codebase default for deployment
i functions: ensuring required API cloudfunctions.googleapis.com is enabled...
i functions: ensuring required API cloudbuild.googleapis.com is enabled...
i artifactregistry: ensuring required API artifactregistry.googleapis.com is enabled...
i functions: Loading and analyzing source code for codebase default to determine what to deploy
Serving at port 8819
i extensions: ensuring required API firebaseextensions.googleapis.com is enabled...
i functions: preparing functions directory for uploading...
i functions: packaged /Users/<Path>/functions (97.26 KB) for uploading
i functions: ensuring required API run.googleapis.com is enabled...
i functions: ensuring required API eventarc.googleapis.com is enabled...
i functions: ensuring required API pubsub.googleapis.com is enabled...
i functions: ensuring required API storage.googleapis.com is enabled...
i functions: generating the service identity for pubsub.googleapis.com...
i functions: generating the service identity for eventarc.googleapis.com...
✔ functions: functions folder uploaded successfully
i hosting[...]: beginning deploy...
i hosting[...]: found 10 files in build/client
✔ hosting[...]: file upload complete
i functions: updating Node.js 22 (2nd Gen) function helloWorld(us-central1)...
✔ functions[helloWorld(us-central1)] Successful update operation.
Function URL (helloWorld(us-central1)): https://helloworld-r6nhcsbmca-uc.a.run.app
i hosting[...]: finalizing version...
✔ hosting[...]: version finalized
i hosting[...]: releasing new version...
✔ hosting[...]: release complete
✔ Deploy complete!
Project Console: https://console.firebase.google.com/project/<Firebaseプロジェクト名>/overview
Hosting URL: https://<Firebaseプロジェクト名>.web.app
Hosting URLにアクセスすれば、先ほどローカルで確認した画面が現れるはずです!
(手順6)GitHubActions設定
(簡易手順)secrets認証によるFirebaseデプロイ
次のようなGitHubActionsが生成されています。
firebase-hosting-merge.yml はmainへのマージ時に商用へデプロイできます。
# 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
jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_DEV_... }}
channelId: live
projectId: <Firebase開発用プロジェクト> #商用プロジェクトに変えておくのがいいかもしれません
firebase-hosting-pull-request.ymlというのも生成されています。
Firebaseには、プレビューという機能があり、PR時にプレビュー用のアプリを公開するものです。
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools
name: Deploy to Firebase Hosting on PR
on: pull_request
permissions:
checks: write
contents: read
pull-requests: write
jobs:
build_and_preview:
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_<Firebaseプロジェクト名> }}
projectId: <Firebase開発用プロジェクト> #商用プロジェクトに変えておくのがいいかもしれません
(応用手順)OIDC認証
デフォルトでは、GitHubのsecretsにGitHub用サービスアカウントのキーが登録されて手軽なのですが、"やや"非推奨みたいです。
- GitHub secretsを使用する方法が非推奨の理由
- 鍵の有効期限がない
- ローテーションが煩雑
- 漏えいリスクがある
ではどうするかというと、
OIDC (OpenID Connect) を使用して認証する方法があります。
- OIDCのメリット
- 秘密鍵の管理が不要
- 一時的な認証情報
- 資格情報のローテーション不要
- リポジトリごとの権限制御
GitHubの特定のリポジトリからしかデプロイを許さず、キーを発行しないので、それ以外の使われ方はあり得ません。
必ず、自分のmainブランチに対してプルリクしないとデプロイできないのです。
OIDC認証の手順: GitHub上の設定(1/3)
作成するWorkflow.ymlには機密情報がなくなり、全てを公開できます。
mainにマージすれば誰でもデプロイできてしまうので、GitHubで事前に以下の設定をしておきましょう。
Setting > Rulesetsと進んで、以下にチェック
- Require a pull request before merging
マージ前にPRが必要になって、いきなりmainにプッシュしなくなります。
OIDC認証の手順: サービスアカウントの設定(2/3)
こちらの記事を参考にしながら、GCP上にサービスアカウントを作成しました。
Firebase へのデプロイを GitHub Actions & Workload Identity によって自動化する
やることをまとめると以下の流れです。
- GitHub Actions が Firebase にアクセスするためのサービスアカウントを作成(ちゃんとプロジェクトがあっているか確認しましょう)
- サービスアカウントに対し、必要なロールを付与。
エラーを出しながら試したところ、Hosting,Functionsをデプロイするのにこの辺りが必要でした。(過不足あると思いますが多めに見てください...)- Cloud RuntimeConfig 管理者
- Firebase 管理者
- Workload Identity ユーザー
- サービス アカウント ユーザー
- Artifact Registry 書き込み
- Billing Account Usage Commitment Recommender の閲覧者
- CodeBuildサービスアカウント
- Cloud Run 管理者
- Eventarc 管理者
- ID プールの作成
- プールにプロバイダを追加
- プロバイダの属性を設定
- サービスアカウントに対してアクセス権を付与
OIDC認証の手順: GitHub Actionsの再設定(3/3)
ついでに以下の点更新しています。
- main以外のブランチにも使いやすいように、デプロイ時にブランチ名のif分岐を追加
- functionsもデプロイできるように変更
# 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
env:
PROJECT_ID: XXXXXXXXXXX # Google CloudプロジェクトIDに置き換え
SERVICE_ACCOUNT_EMAIL: XXXXX@XXXX.iam.gserviceaccount.com # サービスアカウントのメールアドレスに置き換え
jobs:
build_and_deploy:
runs-on: ubuntu-latest
# GitHub ActionsのJobがOIDCトークンを発行することを許可する
permissions:
contents: 'read' # コードをチェックアウトするため
id-token: 'write' # OIDCトークンを生成するため (これがないと認証が失敗します)
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies & build
run: npm ci && npm run build
# working-directoryは不要。ルートにpackage.jsonがあるため。
- name: Install Firebase CLI
run: npm install -g firebase-tools
- name: Authenticate to Google Cloud for Prd
if: github.ref == 'refs/heads/main'
id: 'auth'
uses: 'google-github-actions/auth@v2'
with:
# ワークロードアイデンティティ連携を使用する設定
# GCPのサービスアカウントのメールアドレスを指定
workload_identity_provider: 'projects/${{ env.PROJECT_ID }}/locations/global/workloadIdentityPools/deploy-firebase-prd-pool/providers/firebase-prd-provider'
# あなたのワークロードIDプールとプロバイダのパスに置き換えてください
service_account: ${{ env.SERVICE_ACCOUNT_EMAIL }}
- name: Install Functions dependencies
run: npm ci
working-directory: functions/ # Functionsのディレクトリパスに合わせる
# functionsディレクトリ内で完結するので、最初のnpm ciと競合しない。
# Functionsのコード自体はデプロイ時にFirebaseがビルドしてくれる。
- name: Deploy to Firebase Hosting
if: github.ref == 'refs/heads/main'
# `google-github-actions/auth@v2`が認証情報を環境変数として設定してくれるため、
# firebase-toolsは追加の設定なしに自動的に認証されます
# --force: Firebase CLIがクリーンアップポリシーの設定を強制的に試みます
# Firebase Functionsは、デプロイ時に関数のソースコードなどをCloud Storageバケットにアップロードします。
# このバケットは通常、デプロイ時に自動的に作成されます。
# しかし、このバケット内の古いビルド済み成果物がたまり続けると、ストレージコストが増加したり、管理が煩雑になったりする可能性があります。
run: firebase deploy --force --project ${{ env.PROJECT_ID }} # プロジェクトIDは環境変数から取得
# .github/workflows/firebase-hosting-pull-request-oidc.yml (推奨のOIDC対応版)
name: Deploy to Firebase Hosting on PR (OIDC)
on:
pull_request:
branches:
- main
env:
PROJECT_ID: XXXXXXX
PROJECT_NAME: XXXXXXXX
SERVICE_ACCOUNT_EMAIL: XXXXXXXX@XXXXXX.iam.gserviceaccount.com # サービスアカウントのメールアドレス
jobs:
build_and_preview:
runs-on: ubuntu-latest
permissions:
contents: 'read'
id-token: 'write' # OIDC認証のために必須
pull-requests: 'write' # PRコメント投稿のために必要
steps:
- name: Checkout code
uses: actions/checkout@v4
# OIDC認証
- name: Authenticate to Google Cloud (OIDC)
id: 'auth'
uses: 'google-github-actions/auth@v2'
with:
workload_identity_provider: 'projects/${{ env.PROJECT_ID }}/locations/global/workloadIdentityPools/deploy-firebase-prd-pool/providers/firebase-prd-provider'
service_account: ${{ env.SERVICE_ACCOUNT_EMAIL }}
- name: Install dependencies & build frontend
run: npm ci && npm run build
# Firebase CLI をインストール
- name: Install Firebase CLI
run: npm install -g firebase-tools
# Firebase CLI を直接実行してデプロイ
- name: Deploy to Firebase Hosting Preview Channel
id: deploy
# --expires 1d で1日だけ有効なプレビューチャネル
# echo "deploy_url=$DEPLOY_URL" >> $GITHUB_OUTPUT
# とすると、${{ steps.deploy.outputs.deploy_url }} で呼び出し可能
run: |
DEPLOY_RESULT=$(firebase hosting:channel:deploy ${{ github.event.pull_request.head.ref }} \
--project ${{ env.PROJECT_ID }} \
--expires 1d \
--json)
echo "Full DEPLOY_RESULT JSON:"
echo "$DEPLOY_RESULT"
DEPLOY_URL=$(echo "$DEPLOY_RESULT" | jq -r '."result"."${{ env.PROJECT_NAME }}"."url"')
echo "deploy_url=$DEPLOY_URL" >> $GITHUB_OUTPUT
# PRにプレビューURLをコメントする
- name: Add preview URL to PR comment
uses: marocchino/sticky-pull-request-comment@v2
if: github.event_name == 'pull_request'
with:
header: 'firebase-preview'
message: |
🚀 Live Preview available at: [${{ steps.deploy.outputs.deploy_url }}](${{ steps.deploy.outputs.deploy_url }})
append: false
終わりに
TypeScriptもWebアプリもGitHubActionsもわかっていない状態でReact Routerに挑むのは多少無理がありました。。
随分とボリュームもあり、育休に入ってからだいぶ時間をかけましたが、改めてまとめなおしたことでだいぶ理解できた気がします。



