Rork で生成したアプリを実機で開いた瞬間、「なんだかモッサリしている」と感じたことはないでしょうか。私も何度も経験しました。特に AI 連携を含むアプリは、初期化時に API キー検証・モデルリストの取得・認証トークンのリフレッシュと、いろいろなものを同時に並行させがちで、結果として最初の画面が出るまでに3秒以上かかるケースも珍しくありません。
結論から書いておくと、Rork 生成アプリの起動時間は、ソースコードを大きく書き換えなくても、ほとんどのケースで半分以下にできます。今回はその手順を、私が実際に計測しながら調整したときのログとあわせて紹介します。
まず「起動時間」を正確に計る
最適化は計測から始めるべきだと分かっていても、React Native アプリの起動時間を正しく計るには少しコツが要ります。感覚で「速くなった気がする」で判断すると、数週間後にまた同じ場所で詰まります。
私が使っている計測点は、次の4つです。
- T0: アプリアイコンをタップした瞬間(iOS の
Launch Screen表示開始) - T1: JavaScript バンドルのロード完了(
AppRegistry.runApplicationが走るタイミング) - T2: 最初のルートコンポーネントが
onLayoutを発火するタイミング - T3: ユーザーが最初のインタラクション(タップ等)が可能になるタイミング
計測は React Native の PerformanceObserver よりも、素朴に Date.now() と InteractionManager.runAfterInteractions を組み合わせる方が実用的でした。
// index.js
const t0 = Date.now();
global.__APP_START__ = t0;
// App.tsx
useEffect(() => {
const t1 = Date.now();
console.log(`[startup] bundle->app mount: ${t1 - global.__APP_START__}ms`);
InteractionManager.runAfterInteractions(() => {
console.log(`[startup] interactive: ${Date.now() - global.__APP_START__}ms`);
});
}, []);この地味なログだけで、「どこが重いか」が一気に見えるようになります。私の初回計測では T0→T3 が平均 3,200ms で、内訳は T0→T1(バンドル)が 1,100ms、T1→T2(初期レンダリング)が 800ms、T2→T3(インタラクション可能)が 1,300ms でした。
初期画面で「非同期 await」を連鎖させない
Rork で生成したアプリのルートコンポーネントを開くと、多くの場合 useEffect の中でユーザー情報・テーマ設定・AI モデルリスト・通知権限を順番に await で取っています。ここが起動時間の最大のボトルネックです。
await A(); await B(); await C(); と書かれている処理は、それぞれ独立していれば Promise.all で並列化できます。
// Before(遅い)
const user = await fetchUser();
const theme = await fetchTheme(user.id);
const models = await fetchAvailableModels();
// After(速い)
const [user, models] = await Promise.all([
fetchUser(),
fetchAvailableModels(),
]);
const theme = await fetchTheme(user.id); // userに依存するものだけ後続私はここの書き換えだけで 800ms 短縮できました。依存関係をちゃんと見ると、並列化できるものとできないものがはっきり分かれます。
起動時に読み込む画像を最小化する
もう一つ効いたのが、起動直後に描画するロゴ・オンボーディング画像の扱いです。Rork が自動生成するコードでは、ホーム画面やカルーセルの画像が require() で最初のバンドルに含まれてしまっていることがあります。
画像を遅延ロードに切り替えると、JavaScript バンドル自体が小さくなり、T0→T1 が大幅に改善します。
// Before(バンドルに含まれる)
import heroImage from './assets/hero.png';
<Image source={heroImage} />
// After(起動後にフェッチ)
<Image source={{ uri: 'https://cdn.example.com/hero.png' }} />ただし、ロゴやアイコンは require() のままの方が「画像が一瞬出ない」UX 劣化を防げます。カルーセル・背景・サンプル画像のような「後から見せてよいもの」だけを CDN 化するのが現実的です。
認証トークンの検証は描画後に回す
Rork 生成アプリで私が最もやっかいだと感じたのが、認証トークンの検証処理です。初期化時に verifyToken() のレスポンスを待ってから最初の画面を描画する実装が多いのですが、これはほとんどのケースで後回しにできます。
ユーザーのほとんどはトークンが有効な状態で開くので、楽観的にアプリを表示し、裏で検証する方が体感速度が劇的に改善します。
// Before
if (\!tokenVerified) return <Splash />; // ネットワーク待ち
return <MainApp />;
// After
const [tokenVerified, setTokenVerified] = useState(true); // 楽観的に true
useEffect(() => {
verifyToken().then(valid => {
if (\!valid) {
setTokenVerified(false);
navigation.reset({ routes: [{ name: 'Login' }] });
}
});
}, []);
return <MainApp />;この「楽観的レンダリング」だけで T2→T3 が約 600ms 縮みました。トークンが無効だったときに一瞬だけホーム画面が見えてからログイン画面に飛ぶ挙動になりますが、これは UX としてはむしろ自然です。
Hermes と ProGuard を有効にする
iOS/Android どちらも、ビルド設定の見直しで起動時間は改善します。Rork 生成アプリのデフォルト設定が最新になっているとは限らないので、以下を確認してください。
Android: android/app/build.gradle で enableHermes: true、enableProguardInReleaseBuilds: true を有効化。Hermes は JavaScript の事前コンパイルで起動時間を明確に短縮します。
iOS: ios/Podfile で :hermes_enabled => true、:fabric_enabled => true(New Architecture 対応のアプリのみ)を確認。Hermes は iOS でも有効です。
私のケースでは Hermes 有効化だけで T0→T1 が 400ms 短くなりました。すでに有効になっているプロジェクトも多いですが、古い Rork テンプレートから生成したアプリでは無効のままになっていることがあります。
計測した結果
最終的に、私のアプリは T0→T3 が平均 1,050ms まで縮まりました。3,200ms から考えると約 1/3 です。
- T0→T1: 1,100ms → 550ms(Hermes + バンドル画像削減)
- T1→T2: 800ms → 300ms(Promise.all 並列化)
- T2→T3: 1,300ms → 200ms(楽観的トークン検証)
ここまでやると、ユーザーの「なんだか速くなった」という体感は明確に変わります。ストアレビューにも速度改善のコメントがつき始めました。
次に試すべきこと
まずは自分のアプリで T0→T3 を計測してみてください。感覚ではなく数字を持つと、どの最適化が効くかの優先順位が見えてきます。1,500ms 以下を目指すのが現実的な第一ゴールです。