メインコンテンツまでスキップ

firebase_storage_ts_study_019

第19章:サムネ生成の作戦(どこで作る?)🗺️🖼️✨

この章は「サムネ(小さい画像)」を どういう方針で作るのが一番ラクで事故らないか を決めて、実際のアプリ設計に落とし込む回だよ〜!😆👍 (次の第20章で「Extensions/Functions/AIで自動化🔥」に入れるように、ここで作戦を固める!)


Strategy Map

1) まずサムネって何が嬉しいの?🤔📷

プロフィール画像って、元画像がスマホ写真だと 数MB になることも普通だよね📱💦 そのまま一覧画面(ユーザーリストとか)で全部読み込むと…

  • 読み込みが遅い🐢
  • 通信量が増える📶💸
  • スクロールが重い😵‍💫
  • キャッシュも効きにくい🌀

だから実務ではだいたいこうする👇

  • オリジナル(保管用・拡大表示用)📦
  • サムネ 128(アイコン用)🙂
  • サムネ 512(プロフィール表示用)🧑‍💻✨ (必要なら 1024 も追加📸)

Performance Comparison

2) サムネを作る場所は3択だよ🧩

Three Generation Methods

① クライアント(ブラウザ)で縮小して「1枚だけ」置く🖥️➡️🗜️

向いてる: 速い・簡単・学習向き✨ 弱点: 端末性能に左右される、複数サイズを作ると実装が増える、ユーザーが“デカい画像”を上げたら保存コストは増えがち📦

これは第8章の「アップ前圧縮・リサイズ🗜️」で、すでに強い武器になってるやつだね💪


② アップロード後にサーバー側で「複数サイズ」を生成する⚙️☁️

たとえば Cloud Functions for Firebase(第2世代)Cloud Storageトリガーで、画像が置かれたら自動でサムネを作るやり方! 公式でも、画像アップ時に Node.jsなら sharp / Pythonなら Pillow を使ってサムネ生成できる例があるよ。(Firebase)

向いてる: 実務っぽい🔥 複数サイズが安定、端末差がない 弱点: 初期設定が増える、コストと運用(失敗時の再生成など)も考える必要あり

注意:関数のリージョンは バケットのリージョンと合わせないとデプロイ失敗やレイテンシ原因になりやすいよ。(Firebase)


③ Extensionsで自動化する🧩✨(まずはこれが最強)

「Resize Images(画像リサイズ)」拡張を入れると、アップロードされた画像から複数サイズを自動生成してくれる! しかも、生成先バケットを分けるのが推奨(=関数が不要なファイルでも反応しちゃうのを防ぐ)って公式に書かれてる。(extensions.dev)

向いてる: 最速で“実務感”が出る🚀 / 自作より事故りにくい 弱点: こだわり(特殊な命名・特殊なクロップ)をやりたいときは自作が必要

Resize Images は Blaze(従量課金) が必要だよ。(extensions.dev)


Design Triangle

3) ここを決めれば勝ち🏆(サムネ設計の3点セット)

A. 何サイズを作る?📐

迷ったらこれでOK👇

  • 128x128:ヘッダーやコメント欄の丸アイコン🙂
  • 512x512:プロフィール画面の“そこそこ大きい表示”✨
  • (余裕があれば)1024x1024:高解像度端末用📱✨

「正方形」はUIが綺麗に揃いやすくて、初心者にとっても事故が少ない👍


B. どこに置く?(パス設計)📁

おすすめは「オリジナル」と「サムネ」を分ける👇

  • オリジナル:users/{uid}/profile/original/{fileId}
  • サムネ:users/{uid}/profile/thumb/{fileId}_128x128.jpg みたいに

Resize Images拡張だと、元ファイル名に _{width}x{height} のサフィックスが付いて出力される挙動が基本(例:cat.jpgcat_200x200.jpg)だよ。(extensions.dev)


C. 画面側は「どれを優先して使う?」🖼️

UIルールを決めると、画面実装が一気にラクになる😆

  • アイコン(小):thumb128 があればそれ、なければ original
  • プロフィール(中):thumb512 があればそれ、なければ original
  • 拡大表示:original

Firestore Schema

4) Firestore側の持ち方(超おすすめ形)🧠🗃️

「URLをベタ保存」より、pathを主役にしておくと強い💪 (URLはトークンやキャッシュ都合で変化しうるので、“必要なとき再取得”が安全寄り)

例:users/{uid} にこう持つ👇

  • photo.originalPath
  • photo.thumbPaths.128
  • photo.thumbPaths.512
  • photo.updatedAt
ts
type UserPhoto = {
originalPath: string;
thumbPaths: {
"128"?: string;
"512"?: string;
};
updatedAt: number;
};

Resize Images は 生成画像に新しいダウンロードトークンを付与し、元画像と同じトークンにはならない(=URLが別物になる)挙動があるよ。(extensions.dev) なので「URL保存派」の場合は、各サムネURLも別で保存が必要になりがち。


5) 手を動かす🛠️✨:画面側の“サムネ優先ロジック”を作ろう

5-1. どの画像を使うか選ぶ関数を作る🙂

ts
type PhotoDoc = {
originalUrl?: string; // すでにURLを持ってる場合の逃げ道
thumbUrls?: { "128"?: string; "512"?: string };
};

export function pickPhotoUrl(photo: PhotoDoc | undefined, want: "128" | "512") {
if (!photo) return undefined;
return photo.thumbUrls?.[want] ?? photo.originalUrl;
}

5-2. Reactで使う🧑‍💻✨

tsx
const avatarUrl = pickPhotoUrl(user.photo, "128");
const profileUrl = pickPhotoUrl(user.photo, "512");

return (
<>
<img src={avatarUrl ?? "/default-avatar.png"} width={48} height={48} />
<img src={profileUrl ?? "/default-avatar.png"} width={256} height={256} />
</>
);

これで「サムネが用意されてるなら勝手に軽い方を使う」動きが完成🎉 あとは第20章で 自動生成 を入れれば、現実アプリ感が一気に出る🔥


6) 自動生成の選び方(おすすめルート)🚦

Extension Install

ルートA:Resize Images拡張(最短で“それっぽい”)🧩🚀

やることは超シンプル👇

  1. Extensions から Resize Images をインストール
  2. 「入力バケット」と「出力バケット(分けるの推奨)」を設定(extensions.dev)
  3. 生成サイズを 128x128, 512x512 みたいに登録
  4. アプリ側は「決めた命名規則」で path/URL を作る or Firestoreへ保存する

拡張は メタデータをコピーできたり、キャッシュ系メタデータを扱える仕様もあるよ。(extensions.dev) さらに面白いのが、拡張側に Genkit(Gemini)を使ったコンテンツフィルタ機能(不適切画像の検知など)も用意されてること!😳🤖(extensions.dev)


ルートB:Cloud Functions(自作で自由度MAX)⚙️🔥

公式のトリガー例でも「サムネは thumb_ みたいな接頭辞を付けて、サムネのアップロードで再トリガーしないように除外する」といった実務テクが書かれてるよ。(extensions.dev)

しかも Firebase Functions は Node.js のランタイムとして Node.js 22 / 20 を選べる(Node.js 18は非推奨)と明記されてる。(Firebase) ESM("type":"module")もOK。(Firebase)

「どっち使う?」は、最初は Node.js 20 or 22 でOK👍(新しめを選べばOK)


AI Enrichment

7) AIも絡める🤖🖼️✨(サムネ設計が“次の章”に繋がる!)

サムネの“生成”そのものはAIじゃなくてもできるんだけど、AIを足すと一気に実務っぽくなる🔥

  • **画像の説明文(altテキスト)**を自動生成📝
  • 検索用タグを自動付与🏷️
  • 不適切画像チェックでレビュー行きにする🧯

Firebase AI Logic は **マルチモーダル(画像URL入力OK)**で、画像から説明文を作る系がやれる。(Firebase) さらに App Check 対応も書かれてるので、“正規アプリ以外から叩かれにくくする”も合わせやすいよ。(Firebase) デフォルトの **ユーザーあたりレート制限(例:100 rpm)**の話もあるから、乱用対策の設計にも繋がるね🛡️(Firebase)

あと、モデルの入れ替わりもあるので「いつまで使える?」は要チェック! 例として、あるモデルは 2026-03-31 に引退予定と案内されてたりする。(Firebase)


8) Antigravity / Gemini CLI / Firebase MCP server で“作戦会議”を爆速にする🚀🧠

Gemini CLI に Firebase拡張を入れる(コマンド1発)⚡

Firebase公式が、Gemini CLI拡張のインストール方法を案内してるよ。(Firebase)

bash
gemini extensions install https://github.com/gemini-cli-extensions/firebase/

この拡張は Firebase MCP server を自動で導入・設定して、Firebase操作やドキュメント参照をやりやすくするって説明されてる。(Firebase)

Firebase MCP server(Antigravityも対応)🧩

Firebase MCP server は Antigravity や Gemini CLI などのMCPクライアントから使えて、 Cloud Storage/Firestore の Security Rules を理解するみたいな用途にも効くよ。(Firebase)

Antigravity側の設定例も公式に出てて、firebase-tools@latest mcp を使う形になってる。(Firebase)


9) 超重要⚠️:プランとバケット要件(2026-02-03が絡む話)

もしあなたのプロジェクトが *.appspot.com のデフォルトバケットを使うパターンに該当するなら、2026-02-03 までに Blaze に移行が必要などの要件が案内されてるよ。(Firebase) サムネ自動生成(Extensions/Functions)をやるほど、バケット周りは避けて通れないのでここは早めに確認しよ〜!🧯


ミニ課題📝✨(この章のゴール確認)

  1. あなたのアプリで必要なサムネサイズを 2つ 決める(例:128と512)📐
  2. パス設計を決める(original/thumb/ を分ける)📁
  3. React側で「サムネ優先で表示する関数」を入れる🙂
  4. 自動生成の方式を A(Extension) or B(Functions) のどちらにするか決める🚦

チェック✅(できたら勝ち!)

  • 「どの画面で、どのサイズを使うか」説明できる🙂✅
  • Storage上のパスが、オリジナルとサムネで分かれてる📁✅
  • Firestoreに “path(またはURL)+thumb情報” を持つ方針が言える🧠✅
  • ExtensionかFunctions、どっちで自動化するか決めた🚀✅

次の第20章は、ここで決めた作戦を Extensions/Functions/AIで合体🔥させて、 「アップロード→サムネ生成→説明文/タグ→Firestore反映」まで一気に“現実アプリ”に仕上げるよ😎✨