RORK LABJP
TOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the roundTOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the round
Articles/Dev Tools
Dev Tools/2026-04-22Intermediate

Practical Techniques to Cut the Startup Time of Rork-Generated Apps

If a Rork-generated app feels sluggish on launch, the cause is almost always in how initial loading is assembled. Here's the exact process I used to cut startup time to a third of its original value, measurements included.

rork58performance10startup14optimization5react-native12

When you open a Rork-generated app on a real device for the first time, have you ever thought "this feels kind of heavy"? I've been there many times. Apps with AI integration are particularly susceptible — initialization ends up juggling API key validation, model list fetching, and auth token refresh all in parallel, and it's not unusual for the first screen to take three seconds or more to appear.

To start with the conclusion: startup time in Rork-generated apps can be halved in most cases without major code rewrites. This article walks through the process I used, including the measurement logs I captured as I tuned.

First, measure startup time accurately

Everyone knows "optimization starts with measurement," but measuring React Native startup time correctly takes a little know-how. If you judge by gut feel — "this feels faster now" — you'll hit the same wall again a few weeks later.

These are the four timestamps I use.

  • T0: The instant the user taps the app icon (iOS Launch Screen appearing).
  • T1: JavaScript bundle load complete (when AppRegistry.runApplication fires).
  • T2: The first route component's onLayout fires.
  • T3: The user can first interact (tap, etc.).

For measurement, I've found plain Date.now() combined with InteractionManager.runAfterInteractions more practical than React Native's PerformanceObserver.

// 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`);
  });
}, []);

This mundane log immediately reveals where the heaviness is. My initial measurement showed T0→T3 averaging 3,200ms, broken down as T0→T1 (bundle) 1,100ms, T1→T2 (initial render) 800ms, and T2→T3 (interactive) 1,300ms.

Don't chain await calls on the initial screen

If you open the root component of a Rork-generated app, very often you'll find a useEffect that sequentially awaits user info, theme settings, AI model list, and notification permission. This is the single biggest bottleneck in startup time.

Code written as await A(); await B(); await C(); can be parallelized with Promise.all whenever the calls are independent.

// Before (slow)
const user = await fetchUser();
const theme = await fetchTheme(user.id);
const models = await fetchAvailableModels();
 
// After (fast)
const [user, models] = await Promise.all([
  fetchUser(),
  fetchAvailableModels(),
]);
const theme = await fetchTheme(user.id);  // Only things depending on user come next

Just this rewrite shaved 800ms for me. When you look carefully at dependencies, the separation between "can parallelize" and "can't parallelize" becomes obvious.

Minimize images loaded at startup

Another big win was how logos and onboarding images were handled right after launch. In code that Rork auto-generates, home-screen and carousel images sometimes end up bundled in the initial JS bundle via require().

Switching them to lazy loading shrinks the JavaScript bundle itself, dramatically improving T0→T1.

// Before (included in bundle)
import heroImage from './assets/hero.png';
<Image source={heroImage} />
 
// After (fetched after launch)
<Image source={{ uri: 'https://cdn.example.com/hero.png' }} />

That said, logos and icons are better kept as require() — otherwise you get a "momentary blank image" UX regression. Realistically, only CDN-ify things that can show up a bit later: carousels, backgrounds, sample images.

Move auth token verification to after render

The trickiest part I encountered in Rork-generated apps was the auth token verification flow. Many implementations wait for verifyToken() to respond before rendering the first screen — but in most cases this can be deferred.

The vast majority of users open the app with a valid token, so rendering optimistically and verifying in the background dramatically improves perceived speed.

// Before
if (\!tokenVerified) return <Splash />;  // Waits for network
return <MainApp />;
 
// After
const [tokenVerified, setTokenVerified] = useState(true);  // Optimistically true
 
useEffect(() => {
  verifyToken().then(valid => {
    if (\!valid) {
      setTokenVerified(false);
      navigation.reset({ routes: [{ name: 'Login' }] });
    }
  });
}, []);
 
return <MainApp />;

This optimistic rendering alone cut T2→T3 by about 600ms. When the token is invalid, the user briefly sees the home screen before being redirected to login — but that transition actually feels natural as UX.

Enable Hermes and ProGuard

On both iOS and Android, reviewing build settings improves startup. Rork-generated defaults aren't guaranteed to be current, so check the following.

Android: In android/app/build.gradle, enable enableHermes: true and enableProguardInReleaseBuilds: true. Hermes pre-compiles JavaScript and measurably shortens startup.

iOS: In ios/Podfile, check :hermes_enabled => true and :fabric_enabled => true (for New Architecture apps only). Hermes helps on iOS too.

In my case, just enabling Hermes cut T0→T1 by 400ms. Many projects already have it on, but apps generated from older Rork templates can have it disabled.

Defer heavy stores until after first paint

Another pattern I see often in Rork apps: the root component rehydrates persisted state (MMKV, AsyncStorage, Redux Persist) synchronously before the first render. For anything beyond a few hundred keys, this becomes visible on mid-range devices.

The fix is to split persisted state into two tiers — "needed to render the first screen" (theme, locale, auth flag) and "needed eventually" (draft history, analytics consent, cache of past chats). Rehydrate the first tier synchronously, load the second tier in InteractionManager.runAfterInteractions.

useEffect(() => {
  // Tier 1 already hydrated before mount
  InteractionManager.runAfterInteractions(async () => {
    await hydrateDraftHistory();
    await hydrateAnalyticsConsent();
  });
}, []);

Checking AsyncStorage.getAllKeys().length on app launch is a quick way to find out if this applies to you. Anything above 300 keys is a smell.

Watch for over-eager font loading

React Native's custom font loading is another silent startup cost. If useFonts() from expo-font awaits three to five custom fonts before rendering, you're adding 200-400ms on the first launch after install. System fonts for the first screen, custom fonts loaded in parallel behind the scenes, is a better default.

Final measurements

In the end, my app's T0→T3 average dropped to 1,050ms — roughly a third of the original 3,200ms.

  • T0→T1: 1,100ms → 550ms (Hermes + smaller bundled images)
  • T1→T2: 800ms → 300ms (Promise.all parallelization)
  • T2→T3: 1,300ms → 200ms (optimistic token verification)

Once you reach this point, users notice — the "feels faster" sense becomes unmistakable. Store reviews started including speed-improvement comments.

Pitfalls I ran into

Measuring on the dev menu–enabled build. React Native's dev mode skips JavaScript compilation and adds remote debugging overhead, sometimes inflating numbers by 40-60%. Always measure on a Release build on a real device. My Debug-build numbers looked fine, but Release-build measurements told a different story.

Cold launch vs. warm launch. iOS and Android treat the app differently depending on whether it's already in memory. Numbers from "launch → background → relaunch within 10 seconds" are warm launches and will look artificially fast. Reboot the device before your final measurement.

Chasing the wrong metric. I once spent a whole evening shaving 50ms off T0→T1 when T2→T3 had 900ms of headroom. Always start from the largest segment. The biggest win usually comes from optimizing what's currently at least 40% of total startup.

Over-parallelizing. Promise.all is powerful, but firing 10 network requests simultaneously on a 4G connection can actually serialize at the device's network stack. I cap concurrency at 4-5 requests for any startup batch, which kept perceived parallelism without hitting the mobile radio ceiling.

Comparing iOS and Android numbers

Something that surprised me: the same Rork-generated app showed meaningfully different startup profiles across iOS and Android, and the biggest wins were on different axes. On iOS, enabling Hermes and deferring the token check gave me the largest gains. On Android, the dominant cost was the JavaScript bundle load itself — ProGuard plus asset pruning did more than any JavaScript-level tweak.

If you're shipping to both platforms, measure independently and be prepared to apply different optimizations. Specifically, on Android I found these three checks more impactful than the JavaScript-side changes:

  • Make sure ProGuard/R8 is enabled in the release variant of build.gradle.
  • Check that multiDexEnabled true is not triggering a DEX-load delay by auditing what's actually in the secondary DEX files.
  • Confirm that resConfigs in defaultConfig is narrowed to the locales you actually ship — without this, Android ships every translated string in every support library, bloating the APK by several MB.

On a Pixel 6a running Android 14, these three changes alone took my cold launch from 4,100ms down to 2,800ms before I had even touched the JavaScript layer.

Try this next

Start by measuring T0→T3 on your own app. Having numbers instead of gut feelings makes the priority of each optimization visible. A realistic first target is sub-1,500ms.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-03-14
Rork App Performance Optimization — Rendering, Memory & Network Techniques
Dramatically improve the performance of your Rork-built mobile apps. A practical guide covering rendering optimization, memory management, and network efficiency with code examples.
Dev Tools2026-06-09
Deep Links in Rork Max — Universal Links and URL Schemes
A hands-on guide to deep linking in Rork Max apps: when to use URL Schemes vs. Universal Links, the AASA/assetlinks pitfalls, and the cold-start trap — with working examples.
Dev Tools2026-05-24
Why your Rork app shows a blank screen or loses state after returning from background
Your Rork app sits in the background overnight, you tap the icon the next morning, and the screen is blank — or your drafts have disappeared, or you have been silently logged out. iOS process reclamation, AsyncStorage rehydration timing, and Navigation state restoration each cause a different version of this bug. Notes from running wallpaper and wellness apps with 50M downloads as an indie developer.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →