Expo SDK を上げるたびに、壁紙アプリのネイティブモジュールがどこかで衝突していました。半日かけて依存関係をほどき、動くことを確認して、その日は終わる。新しい機能は何ひとつ増えていません。それが三回続いた時点で、直し続けるより作り直したほうが早いのではないかと考え始めました。
そこで試したのが Rork Max です。UI の生成だけでなく、AdMob の実装から App Store 提出までを一続きにすると謳っていたので、運用中のアプリを丸ごと載せ替える題材にちょうどよいと思いました。半信半疑ではありました。
実際に運用している壁紙アプリをゼロから再実装し、工程ごとに時計を止めて測りました。削れた時間、削れなかった時間、生成コードが取り違えた仕様、そして既存の課金ユーザーをどう引き継いだか。数字を添えて残しておきます。
なぜ「作り直し」という選択をしたのか
再実装のきっかけは、メンテナンスコストの積み重ねでした。React Native製の壁紙アプリは、Expoのバージョンアップのたびにネイティブモジュールとの競合が起きていました。依存関係の解決に半日使うことも珍しくなく、新機能追加より「壊れていないことの確認」に時間を費やすループに陥っていました。
個人開発を続けてきた経験から言うと、この状態は「技術的負債の臨界点」です。修正コストが新規開発コストを超えた瞬間に、作り直しを検討するのが合理的な判断です。
Rork Maxを試すにあたって、以下の条件を設定しました。
- 対象アプリ: 壁紙表示・カテゴリ分類・お気に入り登録・サブスクリプション課金を備えた壁紙アプリ
- 比較基準: 従来手法(React Native + TypeScript)でゼロから実装した際の実績時間
- 測定項目: 工程別の実作業時間・生成コードの品質・App Store審査通過・AdMob表示・収益指標
従来手法での開発時間(参考値)
過去に同規模の壁紙アプリをゼロから作った際の実績です。フルタイムで集中した場合の実作業時間です。
- プロジェクト設定・Expo初期化・ライブラリ選定: 4時間
- UI設計(Figmaワイヤーフレーム): 8時間
- 壁紙一覧・カテゴリ画面の実装: 12時間
- 画像最適化・キャッシュ処理(expo-image): 6時間
- お気に入り機能(AsyncStorage): 3時間
- RevenueCatサブスクリプション統合: 8時間
- AdMob実装(バナー・インタースティシャル・ATT対応): 6時間
- App Store審査対応(プライバシーマニフェスト・スクリーンショット): 4時間
- テスト・バグ修正: 10時間
合計: 約61時間
フルタイムで集中しても最低8日はかかる工程です。この数字は、同じ構成のアプリを何度か作ったことのある人間の時間です。初めて同じ工程を踏む方であれば2〜3倍はかかると思います。
Rork Maxでの実装記録
実際にRork Maxで同じアプリを作り直した際の記録を、時系列で共有します。
Day 1: プロジェクト設定〜基本UI(6時間)
最初のプロンプトはシンプルに書きました。
壁紙アプリを作ってください。
要件:
- カテゴリ別に壁紙を表示(自然、都市、抽象、アニメ)
- ピンチズームで拡大表示
- お気に入り機能(ハートアイコン)
- iOS・Android両対応
- ダークモード対応
デザインは落ち着いた黒背景でミニマルに。
生成されたコードを見て、最初に驚いたのはFlashListの採用でした。FlatListではなくFlashListを使うことで、大量画像のスクロール時のパフォーマンスが最初から確保されていました。以前、自分で実装したときはFlatListで始めてパフォーマンス問題が起きてから移行するという二度手間を踏んでいたので、この選択は経験のある開発者の判断に見えました。
生成されたコードの一部(壁紙リスト画面):
// WallpaperGrid.tsx — Rork Maxが生成したコード
import { FlashList } from '@shopify/flash-list';
import { Pressable, Dimensions, StyleSheet } from 'react-native';
import { Image } from 'expo-image';
import { useRouter } from 'expo-router';
const { width } = Dimensions.get('window');
const ITEM_WIDTH = (width - 3) / 2; // 2列グリッド
interface WallpaperItem {
id: string;
thumbnailUrl: string;
category: string;
}
export default function WallpaperGrid({ category }: { category: string }) {
const router = useRouter();
// wallpapers はZustandストアから取得(生成コードに含まれている)
return (
<FlashList
data={[]} // ストアのデータをここに
numColumns={2}
estimatedItemSize={ITEM_WIDTH * 1.5}
renderItem={({ item }: { item: WallpaperItem }) => (
<Pressable
style={styles.item}
onPress={() => router.push(`/wallpaper/${item.id}`)}
>
<Image
source={{ uri: item.thumbnailUrl }}
style={styles.thumbnail}
contentFit="cover"
transition={200}
cachePolicy="memory-disk" // ← メモリ+ディスクキャッシュ自動設定
/>
</Pressable>
)}
keyExtractor={(item: WallpaperItem) => item.id}
/>
);
}
const styles = StyleSheet.create({
item: { width: ITEM_WIDTH, height: ITEM_WIDTH * 1.5, margin: 0.5 },
thumbnail: { width: '100%', height: '100%' },
});cachePolicy="memory-disk" の指定や transition={200} のフェードイン設定など、実用的な細部がデフォルトで含まれていました。自分が書いても概ね同じ構造になる、という品質です。
Day 2: RevenueCatサブスクリプション統合(4時間)
ここは正直、最も懸念していた工程でした。RevenueCatの統合は、環境変数・アプリID・Entitlement設定・Webhookなどプロジェクト固有の設定が多く、AIツールが「動くコード」を生成しにくい領域です。
実際のプロンプト:
RevenueCatを統合して以下のサブスクリプションを実装してください。
- 月額プレミアム: ¥480/月
- 年額プレミアム: ¥3,800/年(月換算 ¥316)
- 7日間の無料トライアル(年額のみ)
- プレミアム特典: カテゴリ「Premium」の壁紙閲覧、広告非表示
環境変数はプレースホルダーで記述:
EXPO_PUBLIC_REVENUECAT_IOS_KEY / EXPO_PUBLIC_REVENUECAT_ANDROID_KEY
生成されたコードは予想より実用的でした。Entitlement確認のロジック、購入フロー、復元ボタン、エラーハンドリングが一通り含まれていました。
// hooks/useSubscription.ts — RevenueCatとの統合フック
import Purchases, { CustomerInfo, PurchasesPackage } from 'react-native-purchases';
import { useEffect, useState } from 'react';
export function useSubscription() {
const [customerInfo, setCustomerInfo] = useState<CustomerInfo | null>(null);
const [packages, setPackages] = useState<PurchasesPackage[]>([]);
const [loading, setLoading] = useState(true);
// Entitlement「premium」がアクティブかどうかを判定
const isPremium = customerInfo?.entitlements.active['premium'] !== undefined;
useEffect(() => {
async function initialize() {
try {
const info = await Purchases.getCustomerInfo();
setCustomerInfo(info);
const offerings = await Purchases.getOfferings();
if (offerings.current?.availablePackages) {
setPackages(offerings.current.availablePackages);
}
} catch (error) {
console.error('RevenueCat 初期化エラー:', error);
} finally {
setLoading(false);
}
}
initialize();
}, []);
async function purchasePackage(pkg: PurchasesPackage): Promise<boolean> {
try {
const { customerInfo: updated } = await Purchases.purchasePackage(pkg);
setCustomerInfo(updated);
return true;
} catch (error: any) {
// ユーザーがキャンセルした場合はエラーとして扱わない
if (!error.userCancelled) {
console.error('購入エラー:', error);
}
return false;
}
}
async function restorePurchases() {
const updated = await Purchases.restorePurchases();
setCustomerInfo(updated);
}
return { isPremium, packages, loading, purchasePackage, restorePurchases };
}ただし、App Store Connect側のSandboxテスト環境の設定やRevenueCatダッシュボードでのOffering設定については当然含まれていないため、そこは自分で行いました。「コード外の設定作業」は従来と変わりません。詳細なRevenueCat統合の手順については、Rork アプリのRevenueCat収益化実装ガイドも参考になります。
Day 3〜4: AdMob実装とApp Store審査対応(5時間)
AdMobについては、ATT(App Tracking Transparency)対応を含む形で依頼しました。Rork Maxが自動的に expo-tracking-transparency の使用を提案し、UMPコンセントフォームとの統合まで含めたコードを生成した点が印象的でした。
2026年時点では、ATT・UMPの対応はApp Store審査通過の実質的な必須条件です。この最新要件をコードに反映できていたことは、プロダクション投入の観点から評価できます。ATTの最適なプロンプト表示タイミングについては、AdMob ATTオプトイン率を最大化するRork実装で詳しくまとめています。
生成されたAdMob初期化コード(抜粋):
// app/_layout.tsx — ATT許可取得後にAdMobを初期化するパターン
import { useEffect } from 'react';
import { Platform } from 'react-native';
import mobileAds from 'react-native-google-mobile-ads';
// ATT許可取得(iOS 14.5以降)
async function requestTrackingPermission() {
if (Platform.OS !== 'ios') return;
try {
const { requestTrackingPermissionsAsync } =
await import('expo-tracking-transparency');
await requestTrackingPermissionsAsync();
// 許可・拒否に関わらずAdMobを初期化(結果に応じてターゲティングが変わる)
} catch (e) {
// 許可取得失敗時もAdMobは動作する
}
}
export default function RootLayout() {
useEffect(() => {
async function initAds() {
await requestTrackingPermission();
await mobileAds().initialize();
}
initAds();
}, []);
// ... レイアウトの残りのコード
}比較結果:工程別の実作業時間
Rork Maxでの実測値と従来手法の比較です。
- プロジェクト設定・UI基本実装: 24時間 → 6時間(75%削減)
- 画像最適化・キャッシュ処理: 6時間 → 2時間(67%削減)
- お気に入り機能(Zustand): 3時間 → 0.5時間(83%削減)
- RevenueCat統合: 8時間 → 4時間(50%削減)
- AdMob実装: 6時間 → 3時間(50%削減)
- App Store審査対応: 4時間 → 3時間(25%削減)
- テスト・バグ修正: 10時間 → 4時間(60%削減)
合計: 61時間 → 22.5時間(63%削減)
「1/10」という見出しは少し誇張でした。正確には「約1/3」です。ただし、「体感的には1/10」という感覚は正直なところです。コードを書く時間より考える時間・設計する時間が明確に減り、一日に処理できる意思決定の量が変わりました。
特に削減効果が大きかったのは「UI基本実装」です。自分が書いたらどう書くかが頭に入っているぶん、生成コードのレビューが速く終わります。逆に言えば、書き方の型を持っていない段階で使うと、出てきたコードの良し悪しを判断できないまま先へ進んでしまいます。
Rork Maxが苦手だったこと
正直に書きます。Rork Maxが苦手だと感じた領域が3つありました。
1. プロジェクト固有の複雑なビジネスロジック
壁紙アプリで設定した「ダウンロード制限ロジック」(無料ユーザーは1日3枚まで、リセット時刻は日本時間午前0時)の実装で、何度か修正が必要になりました。タイムゾーン処理とローカルストレージの組み合わせで、最初の生成コードは協定世界時(UTC)を基準にしており、日本のユーザーには意図しない挙動をしました。
// ❌ 最初の生成コード(UTC基準のためJSTユーザーに誤動作)
const todayKey = new Date().toISOString().split('T')[0]; // UTC日付
// ✅ 修正後(JST基準に修正)
const getJSTDateKey = () => {
const now = new Date();
// UTC+9(JST)に変換
const jst = new Date(now.getTime() + 9 * 60 * 60 * 1000);
return jst.toISOString().split('T')[0];
};
const todayKey = getJSTDateKey();このような「日本のユーザーを対象とした細かい仕様」は、プロンプトに明示しないとデフォルトでUTC基準になります。日本向けアプリを作る場合は「タイムゾーンはJST(UTC+9)を基準にしてください」とプロンプトに必ず加えることをお勧めします。
2. 既存コードベースへの安全な追加
今回はゼロからの構築だったため問題になりませんでしたが、既存のReact Nativeプロジェクトへの機能追加では難易度が上がると予想しています。既存の型定義や状態管理の構造を理解した上での変更は、現状のRork Maxだけでは完結しにくい印象があります。
3. ネイティブAPIとの深い統合
CoreMLを使ったオンデバイスAI処理や、HealthKitとの統合など、Expo管理外のネイティブAPIとの深い統合では、Rork Maxだけでは完結しませんでした。ゼロからの実装ではなく、生成コードをベースに自分でネイティブ部分を追加する形になります。
リリース後30日間の収益指標比較
再実装したアプリをTestFlightで2週間テストし、App Storeにリリースしました。旧バージョンとの30日間比較です。
- クラッシュ率: 0.8% → 0.2%(74%改善)
- 起動時間(P75中央値): 2.1秒 → 1.3秒(38%短縮)
- サブスクリプション転換率: 2.3% → 2.8%(0.5ポイント向上)
- AdMob eCPM: $0.82 → $0.85(横ばい)
パフォーマンス改善が転換率向上に直接寄与したかどうかは断定できませんが、UIの応答速度が体感で明らかに向上していることは確認しています。クラッシュ率の改善は、FlashListへの移行と画像キャッシュ設計の改善が大きかったと分析しています。
AdMob収益については、eCPMはほぼ横ばいでした。広告収益はATTオプトイン率とインプレッション数に大きく依存するため、アプリの再実装だけで劇的に変わるものではありません。個人開発で広告収益を伸ばそうとしてきた実感としても、効いてくるのはコードの品質より「広告配置の設計」と「ATTオプトイン率」です。その考え方はAdMobで月収150万円を達成した個人開発の収益モデルにまとめています。
既存の課金ユーザーを壊さずに載せ替える
作り直しで一番神経を使ったのは、コードではなく課金の引き継ぎでした。旧バージョンには月額サブスクリプションの購読者がいます。バイナリを差し替えた瞬間に entitlement が切れれば、その日のうちに星1レビューが並びます。
RevenueCat を使っていて助かったのは、購読の実体が App Store 側のトランザクションにあり、SDK は「誰にどの entitlement を紐づけるか」を管理しているだけだという構造でした。ここを取り違えなければ、アプリを丸ごと書き直しても購読は生き残ります。
引き継ぎで実際に確認した項目です。
| 確認項目 | なぜ必要か | 確認方法 |
|---|---|---|
| Bundle ID を変えていない | 変えると別アプリ扱いになり、過去のトランザクションが届かない | 旧プロジェクトの app.json と一字一句比較する |
| App User ID の生成規則が同じ | 匿名IDの規則が変わると同じ人が別ユーザーとして扱われる | RevenueCat のダッシュボードで同一端末が2人に分裂しないか確認する |
| entitlement 識別子の綴り | 生成コードが識別子を勝手に付け替えることがある | ダッシュボードの識別子と生成コードを突き合わせる |
| 復元導線が画面にある | 審査で必ず確認される。無いと差し戻される | 設定画面に「購入の復元」を配置する |
Rork Max が生成した初回コードは、entitlement 識別子を premium にしていました。運用中のダッシュボードでは pro です。この一語がずれるだけで、既存の購読者全員が無料ユーザーとして扱われます。ビルドは通りますし、新規購入のテストも通ります。気づけたのは、実際に課金済みのアカウントでサンドボックス確認をしたときだけでした。
// 生成直後:既存ユーザーの entitlement を見失う
const isPro = customerInfo.entitlements.active['premium'] !== undefined;
// 修正後:識別子はダッシュボードの値をそのまま定数へ
const ENTITLEMENT_ID = 'pro';
const isPro = customerInfo.entitlements.active[ENTITLEMENT_ID] !== undefined;識別子を文字列リテラルのまま各画面に散らさず、定数へまとめておくと、次に作り直すときも一箇所を見れば済みます。生成に任せる範囲が広がるほど、こうした「外部サービス側の固有名詞」は人間が握っておいたほうがよいと感じています。
移行の順番としては、TestFlight に課金済みの実アカウントを1つ入れ、購読が active のまま表示されることを確認してから段階リリースへ進みました。ここを飛ばして全体公開に進むと、気づくのはレビュー欄になります。
22.5時間に入っていない作業
先ほどの22.5時間は、あくまで実装工程の合計です。再リリースまでに実際にかかった時間には、計測から漏れていた作業がいくつもありました。正直に書き出します。
- 旧アプリからの壁紙アセット移行と命名規則の統一: 約5時間
- App Store のスクリーンショットと説明文の作り直し: 約3時間
- 審査差し戻し1回分の対応(サブスクリプションの価格表記漏れ): 約2時間
- 既存ユーザー向けのリリースノート作成と問い合わせ対応: 約2時間
合計で12時間ほどです。実装が22.5時間で終わっても、リリースまでの総所要は35時間近くありました。「開発時間が63%減った」という数字を、そのまま「リリースまでの期間が63%短くなる」と読むと見積もりを外します。
この差を知っていると、次の計画が変わります。今は Rork Max を使う前提の見積もりでも、実装時間と同じくらいの枠を実装外の作業に確保するようにしています。
速くなった分を、どこに戻したか
実装時間が減ったあとで困ったのは、空いた時間の使い道でした。最初の数週間は、単純に次のアプリの構想へ回していました。けれど、作れる本数を増やすことが目的だったのかと自問すると、そうではありませんでした。
今は、削れた時間の多くをリリース後の観察に戻しています。クラッシュログを毎朝見る。レビューを翻訳込みで全部読む。離脱の多い画面をセッション録画で確かめる。以前は「時間がないから月に一度」で済ませていた作業です。
Rork Max が肩代わりしてくれるのは、どう書くかの部分でした。何を作るか、誰に届けるか、いくらで出すか。その問いは相変わらず自分の側に残ります。速くなった分をその問いに戻せたときに、初めてツールを入れた意味が出ると感じています。
個人開発者がRork Maxを試す前に確認しておきたいこと
一本を通してみて、向き不向きがはっきりしました。
向いている用途:
- 壁紙・癒し・習慣トラッカー系など、データ構造がシンプルなアプリ
- MVPを早く市場に出して検証したい場面
- 既存アプリの機能を別プラットフォームに展開したい場合
向いていない用途:
- 既存のReact Nativeコードベースへの大規模な機能追加
- 複雑なオフライン同期・リアルタイム同期の実装
- Unity的な描画処理が必要なゲームアプリ
コスト観点: Rork Maxの月額費用と、節約された開発時間を時給換算で比較すると、今の私の場合は明らかにプラスです。ただし「節約された時間で何を作るか」が重要で、単純にアプリの本数を増やすだけでは意味がないと感じています。
次に Rork Max で作り直す予定なのは、日替わりでコンテンツが増えていくタイプのアプリです。壁紙のようにデータ構造が単純なアプリで通用した手順が、毎日更新される中身を抱えたアプリでも成立するのかを確かめたいと考えています。その記録もいずれここで共有できれば嬉しいです。
まず試してみるなら、小さく始めることをお勧めします。今運営しているアプリの1機能だけをRork Maxで試作し、自分のプロジェクトとの相性を確かめるのが、最もリスクの低い始め方です。