第17章:“uid基準”でデータを持つ準備:ユーザードキュメントの型🧠
この章は「Authでログインできた!」の次に来る、アプリの背骨づくりです💪🙂
やることはシンプルで、ログインした人の uid をキーにして、users/{uid} に“その人のプロフィール箱”を作るだけ📦✨
ここが固まると、次のFirestore章で「ユーザー別データ」「権限制御」が一気にラクになります🚀
0) まず考え方:AuthとFirestoreの役割分担🧠🔐🗂️

- **Auth(認証)**は「本人かどうか」を管理する場所🔐
- **Firestore(データ)**は「アプリとして必要なプロフィール/設定/状態」を置く場所🗂️
Authの user だけでも動くけど、現実のアプリはだいたいこうなります👇
- ニックネーム、アイコン、初回チュートリアル済みフラグ、テーマ設定…などなど🎨🧑💻
→ これを
users/{uid}に集約すると強い💥
そしてセキュリティ的にも、users/{uid} は “本人だけ読める/書ける” ルールにしやすいです✅
(request.auth.uid == uid の形が基本になります)(Firebase)
1) この章のゴール🎯✨
できあがりはこう👇
- ログインしたら、
users/{uid}が無ければ作る(あれば更新だけ)🧱 users/{uid}に、最低限のプロフィール初期値を入れる🧾- “本人だけアクセスできる” ルールの形を知る🛡️
- (おまけ)AIで「プロフィール初期文」や「歓迎メッセージ」を作って入れる🤖💬
2) “ユーザードキュメントの型” を決めよう📐🧾

まずは最小でOK!おすすめの最小セット👇(あとで増やせる前提👍)
uid: string(固定)🆔displayName: string | null(名前)🙂photoURL: string | null(アイコン)🖼️email: string | null(必要なら)📧providerIds: string[](google/password など)🌈createdAt: serverTimestamp()(作成時刻)⏱️updatedAt: serverTimestamp()(更新時刻)🔁lastLoginAt: serverTimestamp()(最終ログイン)🚪profileVersion: number(将来の移行用)🧬
⚠️ パスワードやトークンは絶対保存しないでOKです🙅♂️(Authが管理する領域)
3) Firestoreの準備(最小だけ)🧰🔥
Firestoreがまだならコンソールで有効化して、データベースを作ります🗂️ クライアント(ブラウザ)からFirestoreへ書き込む場合、Security Rulesで許可しないと弾かれるのが普通です(=安全側がデフォ)🛡️(Firebase)
4) 実装:users/{uid} を“初回だけ作る”関数を作る🧱🧠

ここが本章のメインです💪🙂 ポイントは2つ:
onAuthStateChangedは何度も走ることがある(リロード/復帰でも)🔁- だから “何回呼ばれても壊れない(冪等)” にするのが勝ち✨
そこでおすすめは トランザクションです(「無ければ作る、あれば更新」)🧩
4-1) 型(TypeScript)🧾
// src/types/userDoc.ts
import type { Timestamp } from "firebase/firestore";
export type UserDoc = {
uid: string;
displayName: string | null;
photoURL: string | null;
email: string | null;
emailVerified: boolean;
providerIds: string[];
profileVersion: number;
createdAt: Timestamp; // serverTimestampで入れる
updatedAt: Timestamp; // serverTimestampで入れる
lastLoginAt: Timestamp; // serverTimestampで入れる
};
4-2) “ユーザードキュメントを保証する”関数🧱
// src/lib/ensureUserDoc.ts
import type { User } from "firebase/auth";
import { doc, runTransaction, serverTimestamp } from "firebase/firestore";
import { db } from "./firebase"; // 既に作ってる db を使う想定
export async function ensureUserDoc(user: User): Promise<void> {
const ref = doc(db, "users", user.uid);
await runTransaction(db, async (tx) => {
const snap = await tx.get(ref);
const providerIds = user.providerData
.map((p) => p.providerId)
.filter((v): v is string => !!v);
// 毎回更新してOKなもの(lastLoginAt と updatedAt など)
const common = {
uid: user.uid,
displayName: user.displayName ?? null,
photoURL: user.photoURL ?? null,
email: user.email ?? null,
emailVerified: user.emailVerified,
providerIds,
updatedAt: serverTimestamp(),
lastLoginAt: serverTimestamp(),
profileVersion: 1,
};
if (!snap.exists()) {
// 初回だけ作る
tx.set(ref, {
...common,
createdAt: serverTimestamp(),
});
return;
}
// 2回目以降は「上書き事故」を避けて merge
tx.set(ref, common, { merge: true });
});
}
5) どこで呼ぶ?おすすめは onAuthStateChanged の中📌🔁

ログイン状態が変わったときに、ユーザードキュメントを整えちゃいます💪
// 例:AuthProvider 内
import { onAuthStateChanged } from "firebase/auth";
import { auth } from "./firebase";
import { ensureUserDoc } from "./ensureUserDoc";
useEffect(() => {
const unsub = onAuthStateChanged(auth, async (user) => {
if (user) {
await ensureUserDoc(user);
}
// setUser / setLoading とかは今まで通り
});
return () => unsub();
}, []);
✅ これで「ログインできた人は、必ず
users/{uid}がある」状態を作れます✨
6) (発展)“初回ログインだけ” を検出したいとき🌈👀

例えば「初回だけチュートリアル出したい!」みたいな時は、Googleログイン(Popup/Redirect)で返る UserCredential から getAdditionalUserInfo() を使うと便利です🧠
公式のAPIリファレンスにもあります(Firebase)
AdditionalUserInfo には isNewUser がいます(Firebase)
import { GoogleAuthProvider, signInWithPopup, getAdditionalUserInfo } from "firebase/auth";
import { auth } from "./firebase";
export async function signInWithGoogle() {
const provider = new GoogleAuthProvider();
const cred = await signInWithPopup(auth, provider);
const info = getAdditionalUserInfo(cred);
const isNewUser = info?.isNewUser ?? false;
return { user: cred.user, isNewUser };
}
7) Security Rules:users/{uid} は本人だけ🛡️🔐

まずは “超基本形” を入れておくと、事故りにくいです✅
「本人の uid と一致するドキュメントだけ read/write 許可」みたいな形です🧷(Firebase)
// Firestore Security Rules(例)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{uid} {
allow read, write: if request.auth != null && request.auth.uid == uid;
}
}
}
🔥 よくあるエラー:
Missing or insufficient permissionsだいたい「ルールがまだ」「uidの一致条件が違う」「ログインしてない」のどれかです🙂
8) おまけ:AIでプロフィール初期値を“気持ちよく”する🤖✨

たとえば、初回ログイン時に👇
- 「ようこそメッセージ」🎉
- 「プロフィールの自己紹介テンプレ」📝
- 「使い方の一言」💡
を生成して
users/{uid}に入れると、UXがグッと良くなります😊
Firebaseの AI Logic(Gemini) は、アプリからGeminiを呼ぶ導線として整理されています(Firebase) しかも注意点として、特定モデルが 2026-03-31 にリタイア予定の記載もあるので、使うモデル名は新しめを選ぶのが安心です🧠(Firebase)
例(“歓迎メッセージ”生成イメージ)👇
import { getAI, getGenerativeModel } from "firebase/ai";
import { app } from "./firebase";
export async function generateWelcomeText(displayName: string | null) {
const ai = getAI(app);
const model = getGenerativeModel(ai, { model: "gemini-2.5-flash-lite" });
const name = displayName ?? "あなた";
const prompt = `${name}さん向けに、短い歓迎メッセージを日本語で1つ。絵文字多め。`;
const result = await model.generateContent(prompt);
return result.response.text();
}
⚠️ AIに渡す情報は最小でOK(メール全文とか個人情報をモリモリ渡さない)🙆♂️
9) Antigravity / Gemini CLI の使いどころ🚀🧑💻🤖
Antigravity(エージェント開発)🛰️
Antigravityは「エージェントが計画→実装→検証までやる」タイプの開発支援で、Mission Control 的に扱えるよ、という説明が公式Codelabにあります(Google Codelabs)
この章で投げると強い“ミッション例”👇
- 「
ensureUserDocを冪等にして、AuthProviderに組み込んで」🧱 - 「
users/{uid}の型を作って、参照側コンポーネントも生成して」🧾 - 「Firestore Rules の最小形を提案して」🛡️
Gemini CLI(ターミナルのAIエージェント)⌨️
Gemini CLIは、ターミナルから使えるオープンソースのAIエージェントで、MCPやWeb検索などにも触れられています(Google Cloud Documentation) この章だと👇みたいな使い方が刺さります🎯
- 「このリポジトリで
onAuthStateChangedの呼び出し箇所を探して、最適な場所にensureUserDocを差し込んで」🔎 - 「
users/{uid}を読む画面を追加して、型安全にして」🧩
10) ミニ課題🎒✅
ミニ課題A:プロフィール表示ページを作る👤🖼️
users/{uid}をリアルタイム購読(onSnapshot)して表示displayName / email / providerIds / lastLoginAtを出す
ミニ課題B:初回だけ“ようこそ🎉”を出す
getAdditionalUserInfo().isNewUserを使って- 初回ログインならAIで歓迎文生成 → Firestoreに保存 → 画面表示✨
11) チェック問題(サクッと)✅🧠
users/{uid}のuidに メールアドレスを使わない理由は?onAuthStateChangedでensureUserDoc()を呼ぶメリットは?Missing or insufficient permissionsが出たとき、真っ先に疑う場所は?merge: trueを使うと何が嬉しい?- “初回ログインだけ” を検出するヒントは?
(答えの例:1は変更/プライバシー/ID設計的に微妙、2は復帰でも保証、3はRules、4は上書き事故防止、5は getAdditionalUserInfo().isNewUser など🙂)
次につながる話🔗🔥
ここまでで、アプリのデータ設計が「uid中心」に切り替わりました👏✨
次のFirestore章では、この users/{uid} を起点にして
- ユーザーごとのデータ(例:
users/{uid}/posts)🗂️ - 一覧表示・検索・ページング📜
- Rulesの本格設計🛡️
に自然に入れます🚀
必要なら、この第17章の流れに合わせて「プロフィール表示コンポーネント」「購読フック(useUserDoc)」「AI歓迎文の保存」まで、まるごと教材として続きも書くよ🙂✨