TL;DR
- 個人開発iOSアプリのレビューが全然つかない問題を解決するために、開発者同士の相互レビュープラットフォーム「Appyard」を作った
- iTunes RSS FeedでApp Storeレビューを自動検出してポイント付与する仕組みを実装
- Next.js 16 (App Router) + Supabase + Stripe で構築
- Product Hunt的なランキングシステムとレビューエコノミーの2軸で設計
作ったもの
Appyard(アップヤード)— インディーiOS開発者向けの相互レビュー × ランキングプラットフォーム。
ざっくり言うと、個人開発者が自分のiOSアプリを登録して、お互いのアプリをレビューし合うことでApp Storeのレビュー数を増やせるサービスです。
なぜ作ったか
個人開発あるある選手権があったら確実に上位に入る悩み:
「アプリのレビューがつかない」
ダウンロード数がそこそこあっても、レビューを書いてくれる人は1%もいない。でもApp Storeの検索順位にレビュー数は影響するし、新規ユーザーも★の数を見て判断する。
大手アプリならSKStoreReviewControllerのダイアログでなんとかなるかもしれないけど、個人開発の規模だとそもそも母数が少なすぎる。
じゃあ同じ悩みを持つ開発者同士で助け合えばいいじゃん、と。
技術構成
Next.js 16 (App Router)
├── React 19 + TypeScript
├── Tailwind CSS v4 + shadcn/ui
├── Zustand(状態管理)
├── TanStack Query(サーバー状態)
├── Supabase
│ ├── PostgreSQL(DB)
│ ├── Auth(X OAuth 2.0)
│ ├── Edge Functions
│ └── pg_cron(定期実行)
├── Stripe(決済)
└── Vercel(ホスティング)
技術的に面白いところ
1. App Storeレビューの自動検出
これが一番の技術的チャレンジだった。
App Storeにはレビュー取得の公式APIがない(Apple Developer Programに入ればApp Store Connect APIで取れるけど、他人のアプリのレビューは取れない)。
そこでiTunes RSS Feedを使う。
https://itunes.apple.com/jp/rss/customerreviews/id={appId}/json
このエンドポイントから最新レビューが取得できる。レスポンスのJSONにauthor.nameが入っているので、これをAppyardに登録されているユーザーの表示名・ユーザー名と照合する。
// 簡略化したイメージ
const reviews = await fetchItunesReviews(appId);
for (const review of reviews) {
const authorName = review.author.name.label;
// Appyardユーザーとの照合
const matchedUser = await findUserByName(authorName);
if (matchedUser && !alreadyRewarded(matchedUser, appId)) {
await awardPoints(matchedUser, 10);
}
}
これをSupabaseのpg_cronで毎時実行。Vercelのcron機能でトリガーしてる。
精度の問題: author.nameの完全一致だと表記揺れで取りこぼす可能性がある。現状はMVPなのでシンプルな照合にしてるけど、将来的にはファジーマッチングも検討してる。
2. ランキングアルゴリズム
Product Huntを参考にしつつ、独自の要素を加えた。
score = (favorites × 2) + (comments × 1) + (boost_points × 1) × new_bonus
-
new_bonus: 登録から72時間以内なら1.5倍。新しいアプリにチャンスを与えるため - 日次リセット: 毎日 0:00 JST
- 週次リセット: 毎週月曜 0:00 JST
お気に入りの重み(×2)を一番大きくしてるのは意図的で、「ポイントを大量にブーストに使えばランキング1位取れる」みたいなPay to Winにならないようにしてる。コミュニティからの評価が一番大事、という思想。
3. Next.js 16 App Routerでのデータフェッチ戦略
Server ComponentsとTanStack Queryの使い分けにちょっと悩んだ。
方針:
- 初回表示のデータ → Server Componentsで取得(SEO + 初期表示速度)
- ユーザーインタラクション後の更新 → TanStack Queryでクライアントフェッチ
- グローバル状態(認証情報など) → Zustand
ランキングページはServer Componentsでサーバーサイドレンダリングしつつ、お気に入りボタンのクリックとかはTanStack Queryで楽観的更新してる。
4. Supabase Auth × X OAuth 2.0
Xアカウント連携必須にしたのは、開発者コミュニティとの親和性が高いのと、複数アカウント不正の抑制のため。
Supabase AuthでのX OAuth設定自体はドキュメント通りにやればすんなりいく...と言いたいところだけど、コールバックURLの設定ミスで2時間溶かした笑。同じことやる人への忠告:Supabaseダッシュボードに表示されるコールバックURLを一字一句そのままX Developer Portalに貼ること。
DBスキーマ(抜粋)
テーブル設計で意識したのは、user_idのFK先をauth.usersではなくpublic.usersにすること。Supabaseのドキュメントだとauth.usersを参照する例が多いけど、public.usersにプロフィール情報を持たせてそっちを参照した方が、RLSポリシーもJOINもシンプルになる。
主要テーブル:
-
users— ユーザー情報(X連携情報含む) -
apps— 登録アプリ情報(iTunes APIから取得したメタデータ) -
favorites— お気に入り(ユーザー × アプリ、ユニーク制約) -
comments— コメント -
boost_transactions— ブーストポイント消費履歴 -
review_detections— レビュー検出ログ(重複付与防止) -
notifications— 通知
失敗談・ハマったところ
Stripe Webhookのローカルテスト
stripe listen --forward-toでローカルに転送するのは知ってたけど、Supabase Edge Functionsとの組み合わせでWebhookの署名検証がうまくいかず結構ハマった。結局、Edge Functions側でStripeの署名検証をスキップしてた開発時の設定を本番で消し忘れてヒヤッとした。みんなも気をつけて。
iTunes RSS Feedの罠
レスポンスのJSON構造が独特すぎる。entryが1件の時は配列じゃなくてオブジェクトで返ってくるとか、そういう地味なハマりポイントがある。あとレートリミットの明確なドキュメントがないので、ポーリング間隔は保守的に1時間にしてる。
料金体系
- Free: アプリ1件、¥0/月
- Pro: アプリ無制限、¥980/月
- フィーチャー枠: ランキング上部の固定表示枠。1週間¥500 / 1ヶ月¥2,000
Stripe Checkoutでサブスク管理。個人開発×Stripeは本当に相性良い。
まとめ
- App Storeレビューの自動検出は、公式API不在でもiTunes RSS Feedで実現できる
- Next.js 16 App Router + Supabaseの組み合わせは個人開発の生産性が高い
- ランキングアルゴリズムは「お金で買えない」設計が重要
個人開発者の方、ぜひアプリ登録してみてください。登録は1分で終わります。
Appyard: https://app-yard.vercel.app
感想・フィードバック・バグ報告なんでもXで受け付けてます。特にバグ報告は優しめの言葉でお願いします、メンタルが弱いので笑
