LINE LIFF + Firebase + Vercel で月額 0 円運用するためのアーキテクチャ全体図

LINE ミニアプリ開発記 第 2 回

#アーキテクチャ#LIFF#Firebase#Vercel#LINE#個人開発

個人開発で「クラウドに月いくら払うか」は、続けるか潰すかを左右する地味に重い変数です。固定費がゼロなら、PV が伸びなくても放置できる。月数千円かかっていると、忙しいときに止めたくなる。今回作った LINE ミニアプリは 月額 0 円で年単位の運用を回す ことを最初の制約として置きました。

本記事はそのときに組んだ構成図と、各サービスの無料枠を使い切る設計、そして LINE Push の月 200 通制限という個人開発の壁をどう避けたかをまとめます。同じ規模感の個人開発をしている人に、構成のたたき台として読んでもらえる形を目指します。

状況・前提

全体図

LINE アプリ(ユーザー端末)

   │ ① 公式アカウント友だち追加 → リッチメニューから LIFF 起動

LIFF アプリ(Next.js 15 App Router + LIFF SDK)
  Hosted on Vercel Hobby  https://app.既存ドメイン.xxx
  ※ 広告は貼らない。商用利用判定を避ける

   │ ② liff.getIDToken()

Cloud Run functions: verifyLineIdToken (HTTPS、旧 第 2 世代 Functions)
   │ ③ LINE Verify API で検証 → Firebase Custom Token 発行

Firebase Auth (uid = "line:Uxxxx" 形式)

   │ ④ Firestore SDK でクライアントから直接アクセス

Firestore
  └─ users/{uid}/...

Cloud Scheduler → Cloud Run functions: sendDailyReminders (cron)
   ⑤ collectionGroup で対象抽出
   ⑥ uid → LINE userId へ復号 → Messaging API push

Cloud Run functions: lineWebhook (HTTPS)
   ⑦ 公式アカウントの follow / message を受信
   ⑧ あいさつ・LIFF 誘導を返信


[別ホスト: 技術ブログ]
トップドメイン  https://既存ドメイン.xxx
  Hosted on Cloudflare Pages(または GitHub Pages)

   │ Astro で静的配信
   │ AdSense バナー設置

広告収入 → 連載記事の SEO で集客

LIFF 本体 (Vercel) と技術ブログ (Cloudflare Pages 等) を 別のホスティングサービスに分離 しているのがポイントです。理由は次節の通り、Vercel の規約上の制約を避けるためです。

各層が独立した無料枠を持っていて、ほとんどの個人開発は無料枠の合計内で収まる、というのがこの構成のキモです。

月額 0 円のための無料枠戦略

サービス無料枠個人開発で枠を使い切る目安
LIFF 本体配信Vercel Hobby帯域 100GB / 月、Function 呼び出し 100 万回 / 月 など数千 DAU まで
ブログ + 広告配信Cloudflare Pages帯域・リクエスト無制限、ビルド 500 回 / 月まず溢れない
認証Firebase Auth無料月 50,000 アクティブユーザー
DBFirestore1 日 50K 読み取り / 20K 書き込み / 1GiB ストレージ数千 DAU 程度
サーバーCloud Run functions(旧 第 2 世代 Functions)月 200 万リクエスト + 180,000 vCPU-秒 + 360,000 GiB-秒Webhook と cron だけならまず溢れない
通知LINE Messaging API(コミュニケーションプラン)月 200 通後述、ここがボトルネック
ドメインお名前.com 既取得既に支払い済み(年単位)サブドメインは無料
広告Google AdSense設置無料、収益化のみトップドメインのブログに貼る

ボトルネックは LINE Messaging API の月 200 通だけ、というのがこの構成の特徴です。Firestore も Cloud Run functions も、個人開発の規模では無料枠を埋めるほうが難しい。

なぜ LIFF とブログを別ホストに分けるか

ここが一番の落とし穴で、Vercel の Fair Use Guidelines は 「広告掲載を含むデプロイは商用利用」 と明示しています。AdSense を貼ったページを Vercel Hobby で配信すると、規約違反扱いになる可能性が高いです。

The inclusion of advertisements, including but not limited to online advertising platforms like Google AdSense. ─ Vercel Fair Use Guidelines

回避策として 3 つの選択肢があります。

概要コスト
A. 全部 Pro に上げるLIFF もブログも Vercel Pro に統一月 $20
B. ブログを別の無料ホスティングに分離LIFF: Vercel Hobby(広告なし)/ブログ: Cloudflare Pages 等0 円
C. Vercel をやめる全部 Cloudflare Pages や Netlify に乗せる0 円、ただし移行作業

私は 案 B を採っています。LIFF 本体は Vercel の体験が良いのでそのまま、AdSense を貼るブログだけ Cloudflare Pages に逃がす、という二段構え。

LIFF 本体側は 広告を一切貼らない 前提で、Hobby プランでの運用を続けています。ただし「収益化を目指している全体プロジェクトの一部」と判断されると Pro 必須になる解釈もあり、ここは規約の解釈次第のグレーゾーンです。心配なら Vercel サポートに直接問い合わせるのが確実、というのが個人開発で取れる一番堅い動き方かもしれません。

LINE Push 月 200 通制限とどう向き合うか

「リマインダーが本機能なのに 200 通しか送れない」は致命傷に見えます。私が取った対策は次の通りです。

1. 通知をユーザーごとに集約する

ナイーブな実装だと「対象レコード 1 件 = Push 1 通」になりますが、これだと利用者が増えた瞬間に 200 通を越えます。同一ユーザーの該当レコードを 1 通のメッセージに集約 すれば、通数 = アクティブユーザー数まで圧縮できます。

// 悪い例: レコードごとに送る
for (const record of upcomingRecords) {
  await pushTo(record.userId, render(record));
}

// 良い例: ユーザー単位で集約
const grouped = groupBy(upcomingRecords, "userId");
for (const [userId, records] of Object.entries(grouped)) {
  await pushTo(userId, renderDigest(records));
}

2. 多段階通知にして「1 ヶ月前 / 1 週間前 / 前日」を 1 通にまとめる

予定日の 1 ヶ月前・1 週間前・前日にそれぞれ Push を投げる構造にしていますが、運悪く同じ日にこの 3 段階が重なるユーザーがいた場合は、それも 1 通にまとめます。これでユーザー × 日付の総当たりになり、Push 数の見積もりが立てやすい。

3. それでも不足したら有料枠

Messaging API の有料プランに切り替えると 1,000 通から段階的に増やせます。0 円運用は崩れますが、利用者数が広告収益でカバーできる規模になっていれば気にする必要は少ないはずです。

設計上の小ネタ

Firebase Auth の uid を "line:Uxxxx" 形式にする

LINE userId(U で始まる 33 文字)はそのまま auth.uid に使えますが、私はプレフィックスを付けて line:Uxxxx にしました。理由は将来別の認証手段(Twitter / メール)を追加したときに 「どの認証由来の uid か」が一目で分かる ようにしておくためです。Push のときは prefix を取るだけで LINE userId に戻せます。

Firestore は階層型

users/{uid}/children/{cid}/records/{rid} のように階層で持つと、セキュリティルールが match /users/{uid} の入口で uid 一致を確認するだけで配下を全部守れます。フラット構造にすると各コレクションでルールを書き分ける必要があり、個人開発では事故りやすい。

日付は文字列 YYYY-MM-DD で持つ

Firestore の Timestamp 型はタイムゾーンの罠が多いので、「カレンダー上の日」は素直に文字列にしました。並び替えも文字列比較で動きます。これは別記事で深掘り予定。

学び・余談

無料枠で組み切るのは、技術的にはそれほど難しくありません。難しいのは 「無料枠を越えそうなときに何を選ぶか」を先に決めておく ことです。LINE Push が一番先に詰まる、と分かっていれば、コードの書き方も「集約優先」に自然と寄ります。逆に「全部無料で行こう」と漠然と始めると、後でアーキテクチャを書き直す羽目になります。

連載の次回からは、この図のなかの細部(認証フロー、Firestore 階層、cron Push の中身)に踏み込んでいきます。

関連記事


← ブログ一覧へ