バイブコーディングで爆速開発!2日間で画像生成アプリ『Picg』を作った全記録
本記事では、企画から技術選定、実装、そしてストア審査提出までの全工程を公開します。
背景:なぜ作ったのか?
画像生成や動画生成をしたい時が度々あり、期待した結果の出力ができない時は別の生成AIを使ったりするが、クレジット不足やアカウント管理のループがしんどい。
クレジットを気にせず、かつ一つのサービス内で完結したい。Models LabのAPIの利用可能枠が毎月余っている。
じゃあ作ってしまおう、というのが背景。
開発の前提条件
今回の「2日間開発」を成立させるための、前提スキルセットは以下の通りです。
- AIが出力したコーディングをレビューできる知識
- react nativeや各主要ライブラリ群への理解
- SuperbaseとPostgreSQLへの理解
- Appストア関連の知識
これらを有している前提
企画・設計
コンセプト
「直感的UIで、アカウント不要でお手軽かつ無料で使える生成ツール」
ターゲットは「画像生成や動画生成の使い方をワンレベル上げたい人」。
他のユーザーの生成物を「作品」として楽しみつつ、自分でも生成できる体験を目指します。
大まかな流れ
普段はFigmaでデザインを考えるところから始めるが、今回はいきなりコーディングから入る。
頭の中でやりたいことが決まっていたので、ざっくりとUIを作る。
作りながら必要なデータ項目が見えていくので、その後データモデル設計をする。
APIを作り、フロントとバックエンドを繋ぎ込む。
技術選定:スピード重視の構成
今回はExpoではなく、Bare React Native (v0.82.1) を採用しています。
| カテゴリ | 選定技術 | 理由 |
| ルーティング | なし (Custom Tab) | 画面数が少ないため、ボトムタブコンポーネントで表示を切り替え。 |
| 状態管理 | Zustand | グローバル管理は「所持トークン」など最小限に。他はuseStateで十分。 |
| データフェッチ | TanStack Query | キャッシュ管理と無限スクロール実装の容易さから即決。 |
| バックエンド | Supabase | DB構築、認証、Edge Functionsまで一元管理するため。 |
【1日目・前半】バイブコーディングでUI構築
UX(ユーザー体験)として外せないと思ってたのは以下のような点。
- 画像一覧は無限スクロールでシームレスにストレスなく表示
- スクロール時に要素がノッチ部分に被さり、被さっている部分はブラーがかかる(X風の)スクロール
- ローディング中のスケルトンローダーによるUIのガクつきの防止
- モデル選択時はモデル名だけではなく説明や画像サンプルを提供する
- 適当なプロンプトに対して画像生成に適したプロンプトに変換してくれる補完機能
- 日本語で指示をしても裏で適切に英語にしてプロンプトに流し込んでくれる機能
- マニアックな人のためのカスタムパラメータを詳細設定として持たせる
- トークンの仕組みの設計
- サーバー状態による画面遷移、メンテナンス、強制アップデート等
実装TIPS:無限スクロール
useInfiniteQueryを使えば、以下のようにシンプルに実装可能です。
const {
data,
fetchNextPage,
hasNextPage,
// ...他省略
} = usePublicGalleryItems(galleryFilter);
// FlatListのonEndReachedでhandleLoadMoreを呼ぶだけ
スクロール時に要素がノッチ部分に被さるX風のスクロールについては特に理由はないけどなんか良いなと思ってたから今回やりたかった。最近のiPhoneのノッチがドーナツ型なので、スクロール時に画面全体を使い切ってるとやっぱり広く感じる。
モデル選択時の詳細情報取得に関しては、「モデルを選択」っていう行為自体がライトユーザー向けではないんだけど、今回のターゲットが「画像生成や動画生成の使い方をワンレベル上げたい人」なので、セレクトボックスによるモデル名の羅列だけよりは視覚的な情報と簡単な説明から選択できるようにしたかったっていう部分。
トークンの仕組みの設計。
無料で使えて利用回数制限なしを維持するための工夫。
200クレジット付与してあげるからなくなったら後は買ってねだと他と変わらないから
条件さえクリアすればずっと無料で使える(広告さえみてくれれば)のを成立させるために導入。
Models Labの利用可能枠が余っているとはいえ生成一回ごとにコストがかかっているわけで
使用モデルごとにそのコストも異なる。
リワード広告一回あたりの報酬を1円とする(1円〜1.5円と言われているが配給される広告の種類によっても前後すると思うので低めに算定)。
画像生成は、Qwenを例にとると一回あたり0.0047ドル。現在の為替で言うと日本円で0.74円になる。
なのでQwen一回生成ごとに広告一回再生で黒字になるので、初期トークンと回復量を3トークン、Qwenの利用コストを3トークンと設定し、他モデルはこれをベースにしたトークン数を設定している。
とはいえ初期トークン数が少なすぎて全然遊べない問題もありそうなので、定期的に配る等の工夫は必要そう。
生成AIを用いたプロジェクトはサードパーティに強く依存するため、依存先のサーバーで不具合が生じる等こちらで制御できない事態が起きた時のために、きちんとネットワーク不通時専用のスクリーン、メンテナンス用のスクリーンを用意しておきました。
また、開発を進めていくとアプリのバージョンをユーザーに上げさせたいということが将来的に起こる可能性が高いので、最低アプリバージョンの定義をバックエンド側に持たせておき、ユーザーデータには現在のアプリバージョンを保持しておきます。
強制アップデートの配信後は最低アプリバージョンの定義をあげておくことにより、ユーザーはアプリを開くと操作ができず、「ストアアップデートをしてください」と表示させることができます。
この仕組み自体は初めから仕込んでおかないと、強制アップデートをさせるためのアップデートを配信して、そのアップデートをしてくれていないユーザーにはアップデートをそもそもさせられないという地獄が起こります。
【1日目・後半】Supabaseによる堅牢なDB設計
今回のアプリはユーザーログインが不要だが、基本的に大事な処理はフロントでは叩かせない。
モデル一覧やお知らせ一覧、公開データの一覧取得関連以外は全てServiceRole権限にしておくことで、アプリ内でデータを改ざんされたとしてもバックエンドで弾くことができる。
また、所持トークン数などことあるたびにアップデートが走るカラムに関しては、ユーザーテーブルに持たせておくんだけど直接は更新しない。
データを更新するのはトークンヒストリーテーブルというトランザクションテーブルのみにしておいて、トークンヒストリーテーブルにデータが追加された時のpostgreのトリガー関数でユーザーテーブルを更新する。
これでわざわざトランザクションを貼ってデータ更新途中で失敗したらロールバックするとかの処理は不要になる。
そしてAppストアの審査は最大48時間くらいかかるので、変える可能性があるものに関してはできるだけサーバー側で変更できるようにしておきたい。
今回で言うと「モデルの一覧」なんかは必ずDBで定義しておくことで、Supabaseに一行データを入れるだけでアプリ側では新しいモデルが使えるようになる。
DBのmigration周り、RLS周りを一通り作り終えて、フロントと繋ぎ込みをしておく。
【2日目・前半】Edge FunctionsでAPI実装
生成AIのAPI周りを実装。
APIはシンプルで、Edge Functionsの機能を使ってdenoで書いたコード内でmodels labのAPIを呼び出し、結果をDBに格納している。
Models labのapiはフォーマットがとても綺麗に整っているので、モデル一覧のDBのカラムの項目内にapiのendpointやモデルidなどを定義しておくことで、どのモデルを選択されたとしても一つのEdge Functionsを使い回すことが可能。
【2日目・後半】仕上げと審査提出
全全体的に動作を確認しつつ不具合や機能追加をしていく。
実際にはストア審査期間中にコソコソと改修したりしてたので4日間くらいのボリューム感。
そうしてできたのが「とにかく便利な画像生成AIアプリ ピクジー(picg)」です。