App launch performance is within store limits
Why it matters
Apple's watchdog process kills any app that doesn't become interactive within 20 seconds of launch. Google Play triggers an ANR (Application Not Responding) rejection when the main thread blocks for more than 5 seconds. Synchronous I/O in module initialization, blocking API calls before the first render, or missing SplashScreen.hideAsync() calls all cause the app to appear frozen — ISO 25010:2011 time-behaviour failure. A slow launch doesn't just risk rejection; it trains reviewers to view the app as low quality, increasing scrutiny of everything else.
Severity rationale
High because exceeding Apple's 20-second watchdog threshold causes an automatic process kill and rejection, reproducible on any network-constrained device.
Remediation
Move all async startup work behind the splash screen and defer it past first render.
// app/_layout.tsx (Expo Router)
import * as SplashScreen from 'expo-splash-screen';
import * as Font from 'expo-font';
SplashScreen.preventAutoHideAsync();
export default function RootLayout() {
const [ready, setReady] = useState(false);
useEffect(() => {
async function prepare() {
try {
await Font.loadAsync({ 'MyFont': require('./assets/MyFont.ttf') });
} finally {
setReady(true);
await SplashScreen.hideAsync();
}
}
prepare();
}, []);
if (!ready) return null;
return <Slot />;
}
Add a 10-second AbortController timeout to any API call that runs during startup. Test on a low-end device or throttled simulator, not a flagship.
Detection
- ID:
launch-performance - Severity:
high - What to look for: Count all relevant instances and enumerate each. Look for patterns that delay app launch: large synchronous operations in module-level code executed before the first render; blocking API calls in the root component's
useEffectwith no loading state (app appears frozen until data loads);setTimeoutorsetIntervalcalls at startup that delay rendering; heavy image loading without lazy/progressive strategies; use ofexpo-fontorexpo-assetwithoutSplashScreen.preventAutoHideAsync()+SplashScreen.hideAsync()(causing either a flash of unstyled content or an extended black screen). For Android, look forApplication.onCreate()doing heavy work. For iOS, look forapplication(_:didFinishLaunchingWithOptions:)doing synchronous I/O. Also checkapp.jsonfor any explicit delay in the splash screen config. - Pass criteria: App startup path is non-blocking. At least 1 implementation must be verified. Heavy initialization is deferred until after the first render. Splash screen shows during loading and dismisses when the app is ready.
- Fail criteria: Synchronous blocking operations at launch; API calls with no timeout that could make the app unresponsive for >20 seconds (Apple's watchdog threshold); no loading state for async startup dependencies.
- Skip (N/A) when: Never.
- Detail on fail:
"Root component fetches user profile synchronously before first render — app will appear frozen on slow networks"or"SplashScreen.hideAsync() never called — splash screen may persist indefinitely" - Remediation: Apple's watchdog kills apps that don't become responsive within 20 seconds. Google Play rejects apps that trigger ANR (Application Not Responding) dialogs.
- Move all async startup work into async
useEffecthooks with loading states - For font loading with Expo:
await SplashScreen.preventAutoHideAsync(); await Font.loadAsync({ ... }); await SplashScreen.hideAsync(); - Add timeouts to any startup API calls — don't let them block indefinitely
- Test startup time on a low-end device (not just a flagship simulator)
- Move all async startup work into async
External references
- iso-25010:2011 · time-behaviour — Time Behaviour (ISO 25010 Performance Efficiency)
- external · apple-watchdog-launch — Apple Watchdog Termination — app launch timeout (iOS)
Taxons
History
- 2026-04-18·v1.0.0·Initial import from app-store-review-blockers·automated