# Why Is My AI-Generated React Native App So Slow? (Fixes)

> By Lawrence Arya, Founder & CEO of VP0. Published 2026-09-29. 10 min read.
> Source: https://vp0.com/blogs/why-react-native-app-so-slow

Most slow AI-built React Native apps are not slow because of React Native. They are slow because of five habits the model repeats.

**TL;DR.** An AI-generated React Native app usually feels slow for five fixable reasons: you are testing a development build, console.log and heavy work are blocking the JavaScript thread, long lists render as ScrollView or unconfigured FlatList, whole screens re-render on every keystroke, and images are oversized. Measure in a release build, move animation to the native driver or Reanimated, switch big lists to FlashList, and memoize what the profiler flags. Starting screens from a VP0 design keeps component trees shallow from the first prompt.

An AI-generated React Native app is almost never slow because of React Native. It is slow because the model repeats a handful of habits: you are testing a development build that is supposed to be slow, `console.log` calls and heavy work are blocking the JavaScript thread, long lists are rendered all at once, whole screens re-render on every keystroke, and images are downloaded at many times their display size. Each has a known fix, and most take minutes once you have measured which one you have. Prevention helps too: starting screens from a free [VP0](/explore) design gives the agent a clear, shallow structure to reproduce, so you are not debugging a twelve-level tree of wrapper views later. Here is the diagnosis order that finds the problem fastest.

## First, make sure you are measuring the right build

The most common "slow app" is not slow at all; it is a development build. The React Native team is explicit that [JavaScript thread performance suffers greatly in dev mode](https://reactnative.dev/docs/performance), because the runtime is doing extra work to give you warnings and helpful errors, and it tells you to always test performance in release builds. Expo Go and a dev client both run in development mode by default, so a list that stutters there may be perfectly smooth in the build your users install.

Before changing any code, build a release version and put it on a real device, ideally an older, cheaper phone, since that is where your slowest users are. On Expo, a preview or production profile build via EAS gives you exactly that; on a bare project, run the release configuration. If the lag disappears, you have saved yourself a day. If it remains, you now have an honest baseline, and every fix below can be judged against it. The [pre-launch test pass](/blogs/how-to-test-an-ai-built-ios-app-before-you-launch/) covers the rest of that real-device routine.

## Understand the two threads, and why 16.67ms matters

React Native renders on a native main thread while your app logic runs on a JavaScript thread. Phones display at least 60 frames per second, which the documentation translates into a hard budget: at most [16.67ms to produce each frame](https://reactnative.dev/docs/performance). If the JavaScript thread misses that budget, touches feel late, navigation transitions stutter, and anything animated through JavaScript jerks.

The developer menu's Perf Monitor shows both frame rates separately, and that split is your map. A dropping JS frame rate with a steady UI frame rate means your JavaScript is doing too much: logging, parsing, re-rendering, or computing in the middle of an interaction. A dropping UI frame rate points at the native side: too many views, expensive shadows and transparency, or giant images being decoded. Generated code tends to fail on the JS side, which is also the side easiest to fix.

| Symptom | Likely cause | First fix to try |
| --- | --- | --- |
| Everything feels sluggish in Expo Go | Development mode overhead | Measure a release build on a real device |
| Typing in a field lags | Whole screen re-renders per keystroke | Move input state down, memoize siblings |
| Long list stutters or shows blanks | ScrollView or untuned FlatList | FlashList, or tune FlatList props |
| Animations jerk during loading | Animation driven on the JS thread | Native driver or Reanimated |
| Scrolling a photo grid drops frames | Full-size images decoded for thumbnails | Request sized images, use a caching image component |
| App is slow only after navigating a while | Listeners or intervals never cleaned up | Return cleanup functions from effects |

## Find the JavaScript thread blockers

Start with the embarrassing one. Agents love logging, and generated code often ships with `console.log` inside render functions, list items, and network handlers. The React Native docs warn that in a bundled app these statements [can cause a big bottleneck in the JavaScript thread](https://reactnative.dev/docs/performance), including logging from debugging libraries. Search the project for them, delete what you do not need, and strip the rest from production builds with the Babel plugin the docs recommend.

Next, look for work that happens during interactions. Parsing a large JSON response, sorting thousands of items, or formatting every date in a list on each render all compete with the frame budget. Move heavy computation out of render, compute it once when data arrives, and cache the result. For work that can wait until an animation or transition finishes, defer it rather than racing it.

Animations deserve special attention. The Animated API calculates each frame on the JavaScript thread [unless you set `useNativeDriver: true`](https://reactnative.dev/docs/performance), which hands the animation to the native side so it keeps running smoothly even while JavaScript is busy. For gestures and anything more ambitious, [Reanimated](https://docs.swmansion.com/react-native-reanimated/) runs animation logic on the UI thread. Generated code often animates opacity and position through plain state updates; converting those is one of the most visible improvements you can make.

## Fix the lists, where most AI apps lose their frames

Lists are where generated apps suffer most, because the model reaches for the simplest thing that renders: a `ScrollView` with `.map()` over the whole array. That mounts every row at once, which is fine for ten rows and painful for five hundred. The first fix is to use a virtualized list. The second is to configure it. React Native's [FlatList optimization guide](https://reactnative.dev/docs/optimizing-flatlist-configuration) lists the knobs: `windowSize` defaults to 21, meaning ten viewports above and ten below are kept mounted, and lowering it reduces memory and work at the cost of occasional blank space; `getItemLayout` removes async layout measurement when rows share a height, which the guide calls a very desirable optimization; and keeping row components light and stable matters as much as any prop.

For long or complex feeds, the bigger win is often switching to [FlashList](https://shopify.github.io/flash-list/), which keeps props close to FlatList's but recycles cell components under the hood instead of creating new ones as you scroll. In practice that means fewer blank cells and far less JavaScript work on fast scrolls. It is a small migration and a good task to hand the agent with a precise instruction.

Whichever list you use, give rows a stable `keyExtractor`, avoid inline arrow functions and inline style objects in `renderItem` when the profiler shows rows re-rendering, and wrap the row component in `React.memo` so unchanged rows are skipped. A feed built from a [well-structured design](/blogs/how-to-make-react-native-app-look-good/) with one clear row component is much easier to optimize than one where every row is a bespoke nest of views.

## Stop the re-render cascade

The quietest performance killer in generated code is state held too high. A typical agent-written form keeps every field's value in the screen component, so each keystroke re-renders the whole screen: the header, the image, the list below, all of it. On a fast phone you may not notice; on an older one, typing feels like wading.

The fix is structural rather than clever. Keep state as close as possible to where it is used, so a text input owns its own value until submit. Split large screens into components with clear props, and use [`React.memo`](https://react.dev/reference/react/memo) on children that receive the same props most of the time, together with `useCallback` for functions you pass down, so memoization is not defeated by a new function identity on each render. Do not memoize everything by reflex; React's own documentation notes that memo is a performance optimization, not a guarantee, and blanket use adds noise. Profile, find the components that render far more than they should, and fix those.

Global state is the other cascade. A single context that holds the user, the cart, the theme, and the current screen re-renders every consumer when any of them changes. Splitting it into smaller contexts, or using a store with selectors, stops a cart update from repainting the settings screen.

## Images, memory, and the leaks that build up

Images are the native-side culprit. Agents often request the original photo and let the view scale it down, so a 4,000-pixel photo is decoded to fill a 90-pixel thumbnail, roughly 44x more pixels in each direction than the screen can show. Ask your backend or image CDN for sized variants, and use a caching image component such as `expo-image` so the same picture is not downloaded and decoded again on every scroll. The FlatList guide makes the same point: every image in a list is a new instance, and the faster it loads, the sooner the JavaScript thread is free again.

Leaks explain the app that is fine at launch and sluggish after ten minutes. Generated effects frequently subscribe to listeners, start intervals, or open sockets without returning a cleanup function, so every visit to a screen adds another one. Search `useEffect` calls for subscriptions and timers and make sure each returns its cleanup. It is tedious work, and it is exactly the kind of mechanical audit an agent does well once you ask for it explicitly.

Finally, confirm the engine. Hermes is [used by default by React Native](https://reactnative.dev/docs/hermes) and improves start-up time, memory use, and app size, so if an older template or a copied config has switched it off, turning it back on is a free improvement. If you are weighing frameworks because of speed, the [React Native versus Capacitor comparison](/blogs/react-native-vs-capacitor-2026-performance/) explains where each one genuinely wins.

## Key takeaways: speeding up an AI-built React Native app

Measure before changing anything: a release build on an older real phone, with the Perf Monitor telling you whether JavaScript or native rendering is dropping frames. Then work down the usual suspects in order: delete logging from hot paths, move animation to the native driver or Reanimated, virtualize and tune long lists or switch them to FlashList, push state down and memoize what the profiler flags, and serve images at the size they are shown. Give the agent the evidence and a precise instruction for each, not a vague request to make it faster. And prevent the next round by starting screens from a free VP0 design, so the component structure the agent writes is shallow and list-friendly from the first prompt.

## Frequently asked questions

**Why is my AI-generated React Native app so slow?** Usually one of five things: you are judging a development build, the JavaScript thread is blocked by logging or heavy work, a long list is rendered with ScrollView or an unconfigured FlatList, screens re-render far more than they should, or images are far larger than they are displayed. Fix those in order, and start new screens from a VP0 design so the agent generates shallow, list-friendly component trees from the beginning.

**Is React Native itself too slow for a real app?** No. React Native renders native views, and its JavaScript engine, Hermes, is on by default. Plenty of large apps ship on it at a smooth frame rate. When an app is slow, the cause is almost always the code on top of the framework, which is good news, because code can be fixed.

**How do I know whether the lag is JavaScript or native?** Turn on the Perf Monitor from the developer menu. It shows the JS frame rate and the UI frame rate separately. If the JS number drops while the UI number holds, your JavaScript is doing too much work; if the UI number drops, look at heavy views, shadows, oversized images, or layout.

**Should I replace FlatList with FlashList?** For long or complex lists, it is often the single biggest win, because FlashList recycles cell components instead of mounting new ones, and its props are close to FlatList's. For a short list of a dozen rows, a plain map or a tuned FlatList is fine.

**Can I ask the AI to fix the performance itself?** Yes, if you give it evidence. Paste the profiler output or name the slow screen and the measured frame drop, and ask for specific changes: memoize this row, move this animation to the native driver, switch this list to FlashList. A vague "make it faster" tends to produce churn without a measurable gain.

## Frequently asked questions

### Why is my AI-generated React Native app so slow?

Usually one of five things: you are judging a development build, the JavaScript thread is blocked by logging or heavy work, a long list is rendered with ScrollView or an unconfigured FlatList, screens re-render far more than they should, or images are far larger than they are displayed. Fix those in order, and start new screens from a VP0 design so the agent generates shallow, list-friendly component trees from the beginning.

### Is React Native itself too slow for a real app?

No. React Native renders native views, and its JavaScript engine, Hermes, is on by default. Plenty of large apps ship on it at a smooth frame rate. When an app is slow, the cause is almost always the code on top of the framework, which is good news, because code can be fixed.

### How do I know whether the lag is JavaScript or native?

Turn on the Perf Monitor from the developer menu. It shows the JS frame rate and the UI frame rate separately. If the JS number drops while the UI number holds, your JavaScript is doing too much work; if the UI number drops, look at heavy views, shadows, oversized images, or layout.

### Should I replace FlatList with FlashList?

For long or complex lists, it is often the single biggest win, because FlashList recycles cell components instead of mounting new ones, and its props are close to FlatList's. For a short list of a dozen rows, a plain map or a tuned FlatList is fine.

### Can I ask the AI to fix the performance itself?

Yes, if you give it evidence. Paste the profiler output or name the slow screen and the measured frame drop, and ask for specific changes: memoize this row, move this animation to the native driver, switch this list to FlashList. A vague 'make it faster' tends to produce churn without a measurable gain.

---
*Published on the [VP0 Journal](https://vp0.com/blogs). Free to read, index and cite with attribution.*
