Rork でアプリを組んでいて気づいたのは、出てきたバグの多くはチャットに「直して」と伝えれば本当に直る、ということです。実利用レビューでも、遭遇したバグのおよそ7割は手を動かさずに解決でき、残り3割はエクスポートしたコードベースでの手修正が必要だったと報告されています。
この「3割」をどう扱うかで、Rork が頼れる相棒になるか、途中で投げ出す道具になるかが分かれます。私自身、個人開発でアプリを長く運用してきた感覚として、AI に丸ごと任せようとすると詰まる場面ほど、実は「人が直すべき側」だったというケースが多いと感じています。ここでは、その線引きを判断するためのトリアージ手順と、React Native(Expo)のエクスポートコードで実際に頻出する5種類の手当てを、動くコードで整理します。
Rork が直せるバグ、人が直すバグの境界線
まず前提として、Rork はモバイル専用の AI アプリビルダーで、標準プランは React Native(Expo)で iOS / Android のネイティブアプリを生成します。生成されたコードはエクスポートできるので、最終的な手当ては自分の手元でも、チャット経由でも行えます。
経験的に、境界線はこう引けます。Rork が得意なのは「画面上で再現でき、症状を言葉で説明できるバグ」です。ボタンが効かない、レイアウトが崩れる、ナビゲーションが想定と違う、といった見えるバグは、症状を伝えればだいたい直ります。一方、人が直すべきなのは「症状が間欠的、または特定の端末・OS・ネットワーク状態でしか出ないバグ」です。これらは AI に状況を再現させる手がかりが少なく、何度チャットを往復しても堂々巡りになりがちです。
判断の軸は「このバグを、私はチャットに3文で再現説明できるか」です。3文で書けるなら任せる、書けないなら自分で見る。この一点だけでも、無駄なチャット往復がかなり減ります。
トリアージの最初の一手:エラーを「層」で分ける
手を動かす前に、バグがどの層で起きているかを切り分けます。層が分かれば、任せるべきか自分で直すべきかが自動的に決まります。
層 典型的な症状 第一選択
UI / レイアウト層 崩れる・効かない・色が違う Rork に任せる (症状を言葉で説明できる)
ナビゲーション層 戻る挙動・ディープリンク・タブ遷移 まず Rork、再現条件が複雑なら自分で
非同期 / API 層 たまに固まる・無言で失敗する 自分で直す (握りつぶしを目視)
プラットフォーム差異層 iOS では動くが Android で落ちる 自分で直す (実機で分岐を確認)
状態 / 永続化層 再起動で消える・古い値が残る 自分で直す (保存と復元を追う)
下の3層(非同期・プラットフォーム差異・永続化)が、まさに先ほどの「3割」の正体です。ここからは、その3層を中心に、私が実際に頻繁に手当てしている5パターンを順に見ていきます。
手当て①:非同期処理の「握りつぶし」を見つける
生成コードで最も多いのが、エラーを握りつぶす try/catch です。AI は「とりあえず落ちないコード」を書くのが上手なので、catch の中が空、あるいは console.log だけで終わっていることがよくあります。これだと本番では「たまに固まる」「無言で失敗する」症状になり、再現説明ができないため Rork に任せても直りません。
// Before: 生成直後によくある形。失敗が握りつぶされている
async function loadProfile ( userId : string ) {
try {
const res = await fetch ( `https://api.example.com/users/${ userId }` );
const data = await res. json ();
setProfile (data);
} catch (e) {
console. log (e); // ← ここで終わり。ユーザーには何も伝わらない
}
}
ここで手を入れるべきは「HTTP ステータスの確認」と「ユーザーに見える状態への反映」です。fetch は 404 や 500 でも例外を投げないため、res.ok を自分で確認する必要があります。
// After: ステータスを確認し、失敗を状態として持つ
type LoadState =
| { status : "loading" }
| { status : "success" ; profile : Profile }
| { status : "error" ; message : string };
async function loadProfile ( userId : string ) : Promise < LoadState > {
try {
const res = await fetch ( `https://api.example.com/users/${ userId }` );
// fetch は 4xx/5xx でも reject しない。明示的に確認する
if ( ! res.ok) {
return { status: "error" , message: `サーバーエラー (${ res . status })` };
}
const profile : Profile = await res. json ();
return { status: "success" , profile };
} catch (e) {
// ここに来るのはネットワーク到達不能・JSON崩れなど
return { status: "error" , message: "通信に失敗しました。電波状況をご確認ください。" };
}
}
なぜ戻り値を状態オブジェクトにするかというと、画面側で「読み込み中/成功/失敗」を必ず描き分けられるからです。握りつぶしを「見える失敗」に変えるだけで、ユーザーからの「たまにおかしい」という曖昧な報告が激減します。
手当て②:iOS と Android で挙動が割れる箇所
クロスプラットフォームの生成コードは、片方の OS でしか検証されていないことが珍しくありません。私自身、壁紙アプリを iOS と Android の両方で運用していますが、同じコードでも挙動が割れる場所はほぼ決まっています。セーフエリア、戻るボタン、キーボードの3つです。
特に Android の物理・ジェスチャーバックは、生成コードが iOS のスワイプバックだけを想定していると素通りしてしまいます。モーダルを開いている最中のバックを自分で受け止める例がこれです。
import { useEffect } from "react" ;
import { BackHandler, Platform } from "react-native" ;
// モーダル表示中の Android バックを「閉じる」に割り当てる
function useAndroidBackToClose ( isOpen : boolean , onClose : () => void ) {
useEffect (() => {
if (Platform. OS !== "android" || ! isOpen) return ;
const sub = BackHandler. addEventListener ( "hardwareBackPress" , () => {
onClose ();
return true ; // true を返してOS既定の「アプリ終了」を抑止する
});
return () => sub. remove ();
}, [isOpen, onClose]);
}
ポイントは return true です。ここを忘れると、モーダルを閉じると同時にアプリ自体が背面に落ちる、という Android だけの不可解な挙動になります。iOS のシミュレータでは絶対に再現しないので、Rork に「戻ると落ちる」と伝えても再現できません。実機の Android を1台手元に置いて自分で確認するのが結局いちばん速い、というのが私の結論です。
手当て③:リストのスクロールがカクつく
数十件を超えるリストで、生成コードが ScrollView に全要素を並べているとスクロールが重くなります。Rork は見た目を作るのは得意ですが、件数が増えたときの性能までは初期生成で気を配らないことがあります。これは「カクつく」としか言えず再現説明が難しいので、自分で FlatList(または FlashList)へ置き換えるのが定石です。
import { FlatList } from "react-native" ;
// Before の ScrollView + map をやめ、仮想化リストにする
< FlatList
data = { items }
keyExtractor = { ( item ) => item.id }
renderItem = { ({ item }) => < WallpaperCard item = { item } /> }
// 画面外の行を破棄してメモリとフレーム落ちを抑える
removeClippedSubviews
initialNumToRender = { 8 }
windowSize = { 5 }
// 高さが一定なら getItemLayout でスクロール計算を省く
getItemLayout = { ( _ , index ) => ({
length: ROW_HEIGHT ,
offset: ROW_HEIGHT * index,
index,
}) }
/>
getItemLayout は行の高さが固定のときだけ使えますが、効果は大きいです。リスト要素の高さが揃っているグリッド系のアプリなら、これを入れるだけで初回スクロールの引っかかりがなくなります。私の壁紙アプリのサムネイル一覧でも、この置き換えで体感のなめらかさが明確に変わりました。
手当て④:権限とATTの呼び出し順序
広告(AdMob など)やトラッキングを入れると、生成コードが権限ダイアログを出す順序まで面倒を見ないことがあります。iOS では ATT(App Tracking Transparency)の許可を求めるタイミングを誤ると、計測の同意が取れず収益にも響きます。順序の原則は「トラッキング許可 → 広告 SDK 初期化」です。
import { requestTrackingPermissionsAsync } from "expo-tracking-transparency" ;
// 広告SDK初期化の前に必ず ATT を確定させる
async function initAdsWithConsent () {
const { status } = await requestTrackingPermissionsAsync ();
const personalized = status === "granted" ;
// ここで初めて広告SDKを初期化する。
// personalized を渡し、非許可ならノンパーソナライズ広告に切り替える
await initMobileAds ({ requestNonPersonalizedAdsOnly: ! personalized });
}
私はこの順序を、生成直後に必ず最初に確認することをお勧めします。逆順にすると、SDK が許可前のステータスで初期化されてしまい、後からでは取り返しがつきません。これは「収益が伸びない」という形で遅れて表面化し、原因の特定がとても難しい類のバグです。だからこそ、生成直後に自分で順序を確認しておく価値があります。順序の設計を一段深めたい場合は、起動時の同意・課金・広告の初期化順序を扱ったRork アプリのコールドスタート設計 も合わせてご覧ください。
手当て⑤:状態の永続化と復元
「再起動したら設定が消えた」「古い値が残っている」という報告は、ほぼ永続化層の手当て漏れです。生成コードはメモリ上の状態は作っても、保存と復元までは入れ忘れることがあります。AsyncStorage への保存は非同期なので、読み込み完了前に画面が初期値で描かれてしまう競合に注意が必要です。
import AsyncStorage from "@react-native-async-storage/async-storage" ;
import { useEffect, useState } from "react" ;
function usePersistentSetting ( key : string , fallback : string ) {
const [ value , setValue ] = useState (fallback);
const [ hydrated , setHydrated ] = useState ( false ); // 復元完了フラグ
// 起動時に一度だけ復元する
useEffect (() => {
AsyncStorage. getItem (key)
. then (( stored ) => { if (stored !== null ) setValue (stored); })
. finally (() => setHydrated ( true ));
}, [key]);
// 変更があれば保存する(復元完了後のみ)
useEffect (() => {
if (hydrated) AsyncStorage. setItem (key, value);
}, [key, value, hydrated]);
return { value, setValue, hydrated };
}
hydrated フラグを設けているのは、復元が終わる前に保存処理が走って初期値で上書きしてしまう事故を防ぐためです。画面側では hydrated が true になるまでローディングを出すか、初期値での描画を待つようにします。この小さなフラグ一つで、「設定がたまにリセットされる」という再現しづらいバグの大半が消えます。
任せる範囲を広げるための記録の付け方
ここまで5つの手当てを見てきましたが、本当に効くのは「自分が手で直したパターンを記録に残す」習慣です。同じ手当てを2回したら、それは次から Rork へのプロンプトに最初から書き込むべき指示になります。たとえば「fetch のレスポンスは必ず res.ok を確認して」「Android のハードウェアバックを必ず処理して」と最初に伝えておけば、生成段階で潰せる割合が上がり、自分で直す3割が2割、1割へと減っていきます。
AI が下地を作り、人が仕上げる——この役割分担は固定ではなく、自分の記録によって少しずつ AI 側に寄せていけるものだと考えています。まずは次に Rork で何かを直したとき、その手当てを一行メモに残すことから始めてみてください。それが、任せる範囲を安心して広げていく一番確実な足がかりになります。