Use Cases (更新: 2026/6/1)

Claude CodeでFirebase開発を実装する実践ガイド

Claude CodeでFirebase開発を進める手順を、Auth、Firestoreルール、Functions、Hosting、Emulator、環境分離まで実装例付きで解説。

Claude CodeでFirebase開発を実装する実践ガイド

Firebase開発をClaude Codeに任せる前に決めること

Firebaseは、Authentication、Cloud Firestore、Cloud Functions、Hosting、Local Emulator Suiteを組み合わせると、小さなチームでも認証付きWebアプリを短期間で公開できるBaaSです。BaaSは「Backend as a Service」、つまり認証やデータベースなどのバックエンド機能をサービスとして使う仕組みです。

ただし、Firebase開発は「とりあえず動く」状態から「安全に運用できる」状態までの差が大きい分野でもあります。Firestore Security Rulesを甘くすると他人のデータが読めます。Cloud FunctionsでAdmin SDKを使うとSecurity Rulesを通らないため、クライアント側の安全設計とは別のレビューが必要です。開発用プロジェクトと本番プロジェクトを取り違えると、テストデータで本番課金が発生することもあります。

Claude CodeにFirebase実装を任せるときは、画面だけでなく「認証、権限、ルール、エミュレータテスト、デプロイ前確認」までを1つの縦切り単位にします。たとえば「問い合わせチケットを作る画面」なら、フォーム、Firestore保存、Security Rules、ルールテスト、Functions通知、Hosting設定までを同じタスクに含めます。

関連する土台として、認証設計はClaude Codeで認証実装を進める方法、CIの組み込みはClaude CodeでCI/CDを整える実践ガイドも参考になります。Supabaseとの設計差を見たい場合はClaude CodeとSupabase統合が比較材料になります。

flowchart LR
  A["User"] --> B["Firebase Authentication"]
  B --> C["React or Astro UI"]
  C --> D["Cloud Firestore"]
  D --> E["Security Rules"]
  D --> F["Cloud Functions v2"]
  C --> G["Firebase Hosting"]
  H["Emulator Suite"] --> B
  H --> D
  H --> F

この記事では、実際にコピペして検証しやすいVite + React + TypeScriptを例にします。Next.jsやAstroでも考え方は同じですが、環境変数名やルーティングだけは読み替えてください。

最小構成と公式ドキュメント

まずはFirebaseの公式ドキュメントを一次情報として確認します。特にSecurity RulesとEmulator Suiteは仕様差が事故に直結するため、古いブログ記事だけで判断しないほうが安全です。

領域公式リンクClaude Codeに任せる粒度
AuthenticationFirebase AuthenticationログインUI、ユーザープロフィール作成、Auth状態管理
FirestoreCloud Firestoreコレクション設計、読み書き関数、インデックス前提の確認
Security RulesFirestore Security Rulesルール本体、失敗テスト、所有者チェック
Cloud FunctionsCloud Functions for Firebaseサーバー側検証、通知、集計、外部API連携
HostingFirebase HostingSPA配信、キャッシュ、プレビュー、本番デプロイ
EmulatorLocal Emulator Suiteローカル検証、ルールカバレッジ、CIでのテスト
料金Firebase pricing読み取り回数、Functions実行回数、ログ量の見積もり
Claude CodeClaude Code docs作業分割、差分レビュー、テスト実行

私が実装支援でよく使うプロジェクト構成は次の通りです。Claude Codeには、まずこの構成を読ませてから「未作成なら追加、既存があれば既存パターンに合わせる」と依頼します。

.
├─ firebase.json
├─ firestore.rules
├─ firestore.indexes.json
├─ .firebaserc
├─ functions/
│  ├─ package.json
│  └─ src/index.ts
└─ src/
   ├─ lib/firebase.ts
   ├─ lib/tickets.ts
   └─ lib/useAuth.tsx

環境分離を最初に固定する

Firebaseの事故は、実装ミスよりも「devだと思ってprodに書いていた」という運用ミスが目立ちます。Claude Codeにコードを書かせる前に、開発、ステージング、本番のプロジェクトを明示します。

.firebasercではエイリアスを使います。実際のプロジェクトIDは自分のFirebase Consoleで作成したものに置き換えてください。

{
  "projects": {
    "dev": "claudecodelab-firebase-dev",
    "stg": "claudecodelab-firebase-stg",
    "prod": "claudecodelab-firebase-prod"
  }
}

firebase.jsonではFirestoreルール、インデックス、Functions、Hosting、Emulatorを同じファイルで管理します。SPAの場合はrewritesindex.htmlへ流します。

{
  "firestore": {
    "rules": "firestore.rules",
    "indexes": "firestore.indexes.json"
  },
  "functions": [
    {
      "source": "functions",
      "codebase": "default",
      "runtime": "nodejs20"
    }
  ],
  "hosting": {
    "public": "dist",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "headers": [
      {
        "source": "/assets/**",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "public, max-age=31536000, immutable"
          }
        ]
      }
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  },
  "emulators": {
    "auth": {
      "port": 9099
    },
    "functions": {
      "port": 5001
    },
    "firestore": {
      "port": 8080
    },
    "hosting": {
      "port": 5000
    },
    "ui": {
      "enabled": true,
      "port": 4000
    },
    "singleProjectMode": true
  }
}

ローカルでは.env.localにWeb SDK用の公開設定を置きます。FirebaseのWeb API keyは秘密鍵ではありませんが、サービスアカウントJSONやAdmin SDKの認証情報は絶対にフロントエンドへ置かないでください。

VITE_FIREBASE_API_KEY=replace-me
VITE_FIREBASE_AUTH_DOMAIN=claudecodelab-firebase-dev.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=claudecodelab-firebase-dev
VITE_FIREBASE_STORAGE_BUCKET=claudecodelab-firebase-dev.appspot.com
VITE_FIREBASE_APP_ID=replace-me
VITE_USE_FIREBASE_EMULATORS=true

Authenticationとクライアント初期化

次のsrc/lib/firebase.tsは、Firebase App、Authentication、Firestore、Functionsを初期化し、ローカル開発ではEmulator Suiteに接続します。Vite環境の例なので、Next.jsならNEXT_PUBLIC_に読み替えます。

// src/lib/firebase.ts
import { initializeApp, getApp, getApps } from "firebase/app";
import {
  connectAuthEmulator,
  getAuth,
  GoogleAuthProvider,
} from "firebase/auth";
import { connectFirestoreEmulator, getFirestore } from "firebase/firestore";
import { connectFunctionsEmulator, getFunctions } from "firebase/functions";

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,
  appId: import.meta.env.VITE_FIREBASE_APP_ID,
};

const app = getApps().length > 0 ? getApp() : initializeApp(firebaseConfig);

export const auth = getAuth(app);
export const db = getFirestore(app);
export const functions = getFunctions(app, "asia-northeast1");
export const googleProvider = new GoogleAuthProvider();

const shouldUseEmulators =
  import.meta.env.DEV && import.meta.env.VITE_USE_FIREBASE_EMULATORS === "true";

const globalState = globalThis as typeof globalThis & {
  __firebaseEmulatorsConnected?: boolean;
};

if (shouldUseEmulators && !globalState.__firebaseEmulatorsConnected) {
  connectAuthEmulator(auth, "http://127.0.0.1:9099", {
    disableWarnings: true,
  });
  connectFirestoreEmulator(db, "127.0.0.1", 8080);
  connectFunctionsEmulator(functions, "127.0.0.1", 5001);
  globalState.__firebaseEmulatorsConnected = true;
}

ログイン状態はReact Hookに切り出します。初回ログイン時にusers/{uid}を作る処理まで含めると、Firestore側の所有者ルールとつなげやすくなります。

// src/lib/useAuth.tsx
import { useEffect, useState } from "react";
import {
  onAuthStateChanged,
  signInWithPopup,
  signOut,
  type User,
} from "firebase/auth";
import { doc, serverTimestamp, setDoc } from "firebase/firestore";
import { auth, db, googleProvider } from "./firebase";

type AuthState = {
  user: User | null;
  loading: boolean;
  signInWithGoogle: () => Promise<void>;
  logout: () => Promise<void>;
};

export function useAuth(): AuthState {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    return onAuthStateChanged(auth, (currentUser) => {
      setUser(currentUser);
      setLoading(false);
    });
  }, []);

  async function signInWithGoogle() {
    const result = await signInWithPopup(auth, googleProvider);
    await setDoc(
      doc(db, "users", result.user.uid),
      {
        uid: result.user.uid,
        email: result.user.email,
        displayName: result.user.displayName,
        photoURL: result.user.photoURL,
        updatedAt: serverTimestamp(),
      },
      { merge: true },
    );
  }

  return {
    user,
    loading,
    signInWithGoogle,
    logout: () => signOut(auth),
  };
}

ここでClaude Codeに依頼するなら、「Googleログインボタンを作って」では粒度が粗すぎます。次のようにレビュー条件まで渡すと、差分確認が楽になります。

Firebase AuthのGoogleログインを実装してください。
- 対象はVite + React + TypeScript
- src/lib/firebase.tsの既存初期化を再利用
- 初回ログイン時にusers/{uid}をmergeで作成
- ログアウト関数も返す
- 本番のサービスアカウントや秘密鍵は触らない
- 実装後に型エラーとFirestore rulesへの影響を説明

Firestoreのデータ設計とCRUD

実例として「問い合わせチケット」を作ります。ユースケースは3つあります。

1つ目は、ログインユーザーが自分の問い合わせを作成して進捗を見る会員ポータルです。2つ目は、管理者がCloud Functions経由でチケットをクローズするサポート運用です。3つ目は、チケット作成時に通知や集計を行い、問い合わせ対応の抜け漏れを減らす業務ダッシュボードです。

クライアントからは、本人のチケットだけを作成・一覧取得します。Firestoreの読み取りはドキュメント単位で課金されるため、必要以上に広いクエリをClaude Codeに書かせないことが重要です。

// src/lib/tickets.ts
import {
  addDoc,
  collection,
  getDocs,
  limit,
  orderBy,
  query,
  serverTimestamp,
  where,
  type Timestamp,
} from "firebase/firestore";
import { db } from "./firebase";

export type TicketStatus = "open" | "closed";

export type Ticket = {
  id: string;
  userId: string;
  title: string;
  body: string;
  status: TicketStatus;
  createdAt: Timestamp;
  updatedAt: Timestamp;
};

type CreateTicketInput = {
  userId: string;
  title: string;
  body: string;
};

export async function createTicket(input: CreateTicketInput): Promise<string> {
  const title = input.title.trim();
  const body = input.body.trim();

  if (title.length === 0 || title.length > 120) {
    throw new Error("Title must be between 1 and 120 characters.");
  }

  if (body.length === 0 || body.length > 4000) {
    throw new Error("Body must be between 1 and 4000 characters.");
  }

  const docRef = await addDoc(collection(db, "tickets"), {
    userId: input.userId,
    title,
    body,
    status: "open",
    createdAt: serverTimestamp(),
    updatedAt: serverTimestamp(),
  });

  return docRef.id;
}

export async function listMyTickets(userId: string): Promise<Ticket[]> {
  const ticketsQuery = query(
    collection(db, "tickets"),
    where("userId", "==", userId),
    orderBy("createdAt", "desc"),
    limit(20),
  );

  const snapshot = await getDocs(ticketsQuery);

  return snapshot.docs.map((ticketDoc) => ({
    id: ticketDoc.id,
    ...(ticketDoc.data() as Omit<Ticket, "id">),
  }));
}

where("userId", "==", userId)orderBy("createdAt", "desc")の組み合わせでは、プロジェクトによって複合インデックスが必要です。Firebase Consoleのエラーリンクで作るか、firestore.indexes.jsonに残してGit管理します。

{
  "indexes": [
    {
      "collectionGroup": "tickets",
      "queryScope": "COLLECTION",
      "fields": [
        {
          "fieldPath": "userId",
          "order": "ASCENDING"
        },
        {
          "fieldPath": "createdAt",
          "order": "DESCENDING"
        }
      ]
    }
  ],
  "fieldOverrides": []
}

Firestore Security Rulesはフィルターではない

Firestore Security Rulesは「許可されないドキュメントを除外して返すフィルター」ではありません。公式ドキュメントでも説明されている通り、クエリが返す可能性のある結果全体がルールで評価されます。つまり、全チケットを取得してから画面側で本人分だけ表示する実装は安全ではなく、ルールにも拒否されます。

次のfirestore.rulesでは、本人だけが自分のチケットを作成・閲覧できます。更新はタイトルと本文だけに絞り、削除は禁止します。管理者操作はクライアントから直接許可せず、後述のCloud Functionsに寄せます。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    function signedIn() {
      return request.auth != null;
    }

    function isOwner(userId) {
      return signedIn() && request.auth.uid == userId;
    }

    function ticketFieldsAreValid() {
      return request.resource.data.keys().hasOnly([
        "userId",
        "title",
        "body",
        "status",
        "createdAt",
        "updatedAt"
      ])
      && request.resource.data.title is string
      && request.resource.data.title.size() > 0
      && request.resource.data.title.size() <= 120
      && request.resource.data.body is string
      && request.resource.data.body.size() > 0
      && request.resource.data.body.size() <= 4000;
    }

    match /users/{userId} {
      allow create, read, update: if isOwner(userId);
      allow delete: if false;
    }

    match /tickets/{ticketId} {
      allow create: if signedIn()
        && request.resource.data.userId == request.auth.uid
        && request.resource.data.status == "open"
        && ticketFieldsAreValid();

      allow read: if signedIn()
        && resource.data.userId == request.auth.uid;

      allow update: if signedIn()
        && resource.data.userId == request.auth.uid
        && request.resource.data.userId == resource.data.userId
        && request.resource.data.status == resource.data.status
        && request.resource.data.diff(resource.data).affectedKeys()
          .hasOnly(["title", "body", "updatedAt"])
        && ticketFieldsAreValid();

      allow delete: if false;
    }

    match /adminStats/{docId} {
      allow read, write: if false;
    }
  }
}

Claude Codeのレビュー観点は、request.auth.uidresource.data.userIdの比較、request.resource.data.keys().hasOnlyによる余計なフィールドの拒否、delete: if falseの明示、管理者操作をクライアントに開けていないことです。ルールが長くなったら「関数化して可読性を上げる」までは任せられますが、権限境界の判断は人間が最終確認します。

Emulator Suiteでルールをテストする

Emulator Suiteは、ローカルでAuth、Firestore、Functions、Hostingを動かすための開発環境です。Firestoreルールの失敗ケースをテストにすることで、Claude Codeが後続の修正で権限を緩めたときに気づけます。

依存関係を入れます。

npm install -D vitest @firebase/rules-unit-testing firebase
firebase setup:emulators:firestore

テストはtests/firestore.rules.test.tsのように置きます。

// tests/firestore.rules.test.ts
import { readFileSync } from "node:fs";
import {
  assertFails,
  assertSucceeds,
  initializeTestEnvironment,
  type RulesTestEnvironment,
} from "@firebase/rules-unit-testing";
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { doc, getDoc, setDoc, updateDoc } from "firebase/firestore";

let testEnv: RulesTestEnvironment;

beforeAll(async () => {
  testEnv = await initializeTestEnvironment({
    projectId: "claudecodelab-firestore-rules",
    firestore: {
      rules: readFileSync("firestore.rules", "utf8"),
    },
  });
});

beforeEach(async () => {
  await testEnv.clearFirestore();
});

afterAll(async () => {
  await testEnv.cleanup();
});

describe("tickets security rules", () => {
  it("allows the owner to create and read a ticket", async () => {
    const aliceDb = testEnv.authenticatedContext("alice").firestore();
    const ticketRef = doc(aliceDb, "tickets/ticket-1");

    await assertSucceeds(
      setDoc(ticketRef, {
        userId: "alice",
        title: "請求書を再送してほしい",
        body: "4月分の請求書が見つかりません。",
        status: "open",
        createdAt: new Date(),
        updatedAt: new Date(),
      }),
    );

    await assertSucceeds(getDoc(ticketRef));
  });

  it("blocks another user from reading the ticket", async () => {
    await testEnv.withSecurityRulesDisabled(async (context) => {
      await setDoc(doc(context.firestore(), "tickets/ticket-2"), {
        userId: "alice",
        title: "契約内容の確認",
        body: "現在のプランを確認したいです。",
        status: "open",
        createdAt: new Date(),
        updatedAt: new Date(),
      });
    });

    const bobDb = testEnv.authenticatedContext("bob").firestore();
    await assertFails(getDoc(doc(bobDb, "tickets/ticket-2")));
  });

  it("blocks status changes from the web client", async () => {
    await testEnv.withSecurityRulesDisabled(async (context) => {
      await setDoc(doc(context.firestore(), "tickets/ticket-3"), {
        userId: "alice",
        title: "ログインできない",
        body: "Googleログインでエラーになります。",
        status: "open",
        createdAt: new Date(),
        updatedAt: new Date(),
      });
    });

    const aliceDb = testEnv.authenticatedContext("alice").firestore();
    await assertFails(
      updateDoc(doc(aliceDb, "tickets/ticket-3"), {
        status: "closed",
        updatedAt: new Date(),
      }),
    );
  });

  it("keeps the test runner alive", () => {
    expect(testEnv).toBeDefined();
  });
});

package.jsonには、ローカル検証用のスクリプトを入れます。CIでも同じコマンドを使うと、手元だけ通る問題を減らせます。

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "firebase:use:dev": "firebase use dev",
    "firebase:emulators": "firebase emulators:start --only auth,firestore,functions,hosting",
    "test:rules": "firebase emulators:exec --only firestore \"vitest run tests/firestore.rules.test.ts\"",
    "deploy:stg": "firebase use stg && npm run build && firebase deploy --only hosting,firestore:rules,firestore:indexes,functions",
    "deploy:prod": "firebase use prod && npm run build && firebase deploy --only hosting,firestore:rules,firestore:indexes,functions"
  }
}

Cloud Functionsに寄せる処理

Cloud Functionsは、クライアントに任せると改ざんされる処理、外部APIキーを使う処理、集計や通知のようなサーバー側処理に使います。ここでは、チケットを閉じるCallable Functionと、チケット作成時に管理者向け通知を作るFirestoreトリガーを実装します。

// functions/src/index.ts
import { initializeApp } from "firebase-admin/app";
import { FieldValue, getFirestore } from "firebase-admin/firestore";
import { onDocumentCreated } from "firebase-functions/v2/firestore";
import { HttpsError, onCall } from "firebase-functions/v2/https";

initializeApp();

const db = getFirestore();

export const closeTicket = onCall(
  {
    region: "asia-northeast1",
  },
  async (request) => {
    if (!request.auth) {
      throw new HttpsError("unauthenticated", "Sign-in is required.");
    }

    const ticketId = request.data?.ticketId;
    if (typeof ticketId !== "string" || ticketId.length > 100) {
      throw new HttpsError("invalid-argument", "ticketId is invalid.");
    }

    const ticketRef = db.doc(`tickets/${ticketId}`);
    const ticketSnap = await ticketRef.get();

    if (!ticketSnap.exists) {
      throw new HttpsError("not-found", "Ticket was not found.");
    }

    const ticket = ticketSnap.data();
    if (ticket?.userId !== request.auth.uid) {
      throw new HttpsError("permission-denied", "You cannot close this ticket.");
    }

    await ticketRef.update({
      status: "closed",
      closedAt: FieldValue.serverTimestamp(),
      updatedAt: FieldValue.serverTimestamp(),
    });

    return { ok: true };
  },
);

export const notifyTicketCreated = onDocumentCreated(
  {
    document: "tickets/{ticketId}",
    region: "asia-northeast1",
  },
  async (event) => {
    const ticket = event.data?.data();
    if (!ticket) return;

    await db.collection("adminNotifications").add({
      type: "ticket_created",
      ticketId: event.params.ticketId,
      title: ticket.title,
      userId: ticket.userId,
      createdAt: FieldValue.serverTimestamp(),
      read: false,
    });
  },
);

重要な落とし穴は、Admin SDKがFirestore Security Rulesをバイパスすることです。Functions内のticket?.userId !== request.auth.uidのような検証を省くと、クライアント側ルールが堅くてもサーバー側で権限事故が起きます。Claude Codeには「Admin SDKはルールを通らない前提で、Function内で入力検証と所有者検証を入れて」と明示します。

Hostingとデプロイ前チェック

Hostingは簡単に公開できますが、公開が簡単な分だけレビューを固定します。プレビューで確認し、ステージングに出し、本番はタグや承認後にします。

firebase login
firebase use dev
npm run build
firebase emulators:start --only auth,firestore,functions,hosting
firebase hosting:channel:deploy preview-firebase-ticket
firebase use stg
firebase deploy --only hosting,firestore:rules,firestore:indexes,functions

Claude Codeにレビューさせる観点は、firebase useの対象、.firebasercのプロジェクトID、firebase.jsonのpublicディレクトリ、SPAのrewrites、静的アセットのキャッシュ、Functionsのリージョン、Firestore RulesとIndexesの同時デプロイです。特に本番デプロイは、Claude Codeにコマンド案を出させても、人間が対象プロジェクトを確認してから実行します。

具体的な失敗例と回避策

失敗例1は、Security Rulesをallow read, write: if request.auth != null;で済ませることです。ログインユーザーなら全員のデータを読めます。本人所有のデータは必ずresource.data.userId == request.auth.uidで縛ります。

失敗例2は、画面側で絞り込む前提のクエリです。getDocs(collection(db, "tickets"))で全件を取得し、React側でuserIdを比較する実装は、コスト面でもセキュリティ面でも不適切です。クエリ自体を本人範囲にします。

失敗例3は、FunctionsでAdmin SDKを使うのに所有者検証を省くことです。Cloud Functionsは便利ですが、Security Rulesとは別の権限境界です。Callable Functionではrequest.auth、入力型、対象ドキュメントの所有者を毎回検証します。

失敗例4は、Emulatorにルールファイルを読み込ませていないことです。Local Emulator Suiteは設定によっては開いた状態に見えることがあります。firebase.jsonfirestore.rulesと、テストで読み込むfirestore.rulesが一致しているか確認します。

失敗例5は、開発プロジェクトと本番プロジェクトを同じブラウザタブで切り替え続けることです。firebase useの結果、.env.local、Firebase ConsoleのURLを作業前に声に出して確認するくらいでちょうどよいです。

コストとセキュリティのレビュー観点

Firebaseの料金は変わることがあるため、最新金額はFirebase pricingで確認します。記事やAIの回答に古い無料枠や単価が残っていることがあるので、金額を断定して設計しないほうが安全です。

レビューでは、Firestoreの読み取り回数、一覧画面のlimit、リアルタイム購読の解除、Functionsのリージョンとタイムアウト、ログ出力量、Hostingのキャッシュを見ます。問い合わせ一覧のような画面では、毎回全件読み直すだけでコストと体感速度が悪化します。

セキュリティでは、サービスアカウントJSONをリポジトリに置かない、FIREBASE_TOKENのようなCI秘密情報をClaude Codeのプロンプトに貼らない、本番プロジェクトへのOwner権限を普段使いしない、App Checkや追加の不正利用対策を検討する、といった観点を入れます。Claude Codeの作業権限は「必要なファイルだけ編集、デプロイはしない、差分とテスト結果を報告」に絞るのが現実的です。

Claude Codeへの依頼テンプレート

Firebase開発で成果が安定する依頼文は、作業対象、禁止事項、検証方法、レビュー観点が入っています。

Firebaseの問い合わせチケット機能を実装してください。

対象:
- Vite + React + TypeScript
- Firebase Auth, Firestore, Cloud Functions v2, Hosting
- 対象ファイルはsrc/lib/firebase.ts, src/lib/useAuth.tsx, src/lib/tickets.ts, firestore.rules, functions/src/index.ts, firebase.jsonのみ

要件:
- Googleログイン済みユーザーだけがticketsを作成できる
- 本人だけが自分のticketsを読める
- status変更はWebクライアントから禁止し、Callable Functionで行う
- Emulator SuiteでFirestore Rulesの成功/失敗テストを追加する
- dev/stg/prodのFirebaseプロジェクトを混同しない

禁止:
- サービスアカウントJSONを作成または表示しない
- 本番デプロイを実行しない
- allow read, write: if trueを使わない

完了報告:
- 変更ファイル
- 実行したテスト
- ルール上の権限境界
- 残る手動確認

この粒度なら、Claude Codeは実装担当、人間はプロダクト判断と権限判断に集中できます。

まとめ

FirebaseとClaude Codeの相性は良いです。理由は、Firebaseが認証、DB、Functions、Hosting、Emulatorを明確なファイルとコマンドで管理でき、Claude Codeがその差分を作りやすいからです。一方で、Security Rules、Admin SDK、環境分離、課金は「動いたからOK」にすると危険です。

実務では、まずEmulator Suiteで縦切り機能を作り、Firestore Rulesの失敗テストを入れ、Functionsの権限検証をレビューし、Hostingはプレビュー経由で確認します。そのうえで、ステージングから本番へ進めると、速度と安全性のバランスが取りやすくなります。

ClaudeCodeLabでは、Claude Codeを使ったFirebase実装、既存アプリのSecurity Rulesレビュー、Emulator Suiteを含む研修、チーム向けの開発ルール整備を支援しています。問い合わせや社内研修の相談では、現在のFirebase構成、困っている機能、デプロイ運用の状態を共有してもらえると、実装支援の範囲を切り分けやすくなります。

この記事で紹介した内容を実際に試すときの確認ポイントは、開発用Firebaseプロジェクトを使っていること、firebase use.env.localが一致していること、Security Rulesの成功/失敗テストが通ること、Cloud Functions内で所有者検証をしていること、本番デプロイをClaude Codeに自動実行させないことです。ここまで確認してから小さな機能を1つ公開すると、Firebase開発の速度を上げながら事故を減らせます。

#Claude Code #Firebase #Firestore #Cloud Functions #BaaS
無料

無料PDF: Claude Code はじめてのチートシート

まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。

スパムは送りません。登録情報は厳重に管理します。

Claude Codeを仕事で使える形にしませんか?

無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。

Masa

この記事を書いた人

Masa

Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。