●MAX — Rork Max generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage●PUBLISH — Rork Max ships 2-click App Store publishing and runs $200/month●RN — The standard Rork builds native iOS/Android apps with React Native (Expo) — the quicker path to a working app●PRICE — Rork is free to start, with paid plans from $25/month●FUND — Rork raised $2.8M from a16z; the platform now sees 743k+ monthly visits with 85% growth●FLOW — Describe your app in plain English and Rork generates deployable code that can use the camera, notifications, and more●MAX — Rork Max generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage●PUBLISH — Rork Max ships 2-click App Store publishing and runs $200/month●RN — The standard Rork builds native iOS/Android apps with React Native (Expo) — the quicker path to a working app●PRICE — Rork is free to start, with paid plans from $25/month●FUND — Rork raised $2.8M from a16z; the platform now sees 743k+ monthly visits with 85% growth●FLOW — Describe your app in plain English and Rork generates deployable code that can use the camera, notifications, and more
When Rork-Built Lists Stutter: Designing Image Caching and Prefetch
A FlatList from Rork starts stuttering once the images pile up. Here is how I restore smoothness with expo-image caching, recyclingKey, prefetch, and a move to FlashList, with the device numbers I measured.
When I first ran an image gallery built with Rork on a real device, the first few cells scrolled smoothly, but the stutter grew more obvious the longer I kept scrolling. The Simulator never showed it; it only surfaced on a slightly older phone in my hand.
The cause was easy to guess. Each cell was re-fetching its image from the network and decoding it again every time it appeared. The FlatList Rork generates is clean and readable, but it does not design for the load that arrives once the image count grows.
As an indie developer who has built image-heavy screens like wallpaper apps many times for the App Store, I want to record here the full path from the list Rork emitted to smooth scrolling again, with the numbers I measured on device.
Why scrolling stutters
The real nature of stutter is usually a fight over the main thread. The screen redraws 60 times a second (120 on some devices), and when a large image decode cuts in between those frames, one frame misses its deadline and the view jumps.
The first job is to isolate the cause. I suspect things in this order. First, is the image re-fetched from the network every time. Second, even with a cache, is the in-memory decoded image being reused. Third, is an unnecessary re-render happening when the cell is recycled. Clear those three in order and most stutter settles.
This works, but the built-in Image has weak cache control and tends to decode again every time a cell is recycled. Past a few dozen images it surfaces as scroll hitching. On my own device, this implementation made memory usage climb in steps throughout the scroll, and on the older phone the screen started responding a beat late.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦A step-by-step way to isolate FlatList stutter from the angle of image decoding and memory
✦Concrete expo-image cache settings plus recyclingKey to stop repeated decoding
✦A prefetch design that loads ahead of the visible range, and the line at which to move to FlashList
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
The first improvement is to swap Image for expo-image. It keeps a two-tier disk and memory cache, and passing recyclingKey suppresses both the mix-up and the re-decode when a cell is reused.
The piece that matters here is recyclingKey. List cells are reused while their contents swap, so without a key the previous image can linger for a moment, or the same image gets decoded again and again. Use a stable value like item.id as the key and expo-image correctly knows that the cell's contents changed.
Set cachePolicy to memory-disk and an image read once stays in both memory and disk, so re-display never re-fetches. This change alone dropped scroll-time memory by about 40% in my setup and the hitching nearly disappeared.
Loading ahead of the visible range
Once caching stops the re-decode, the next step is to prepare the images that are about to appear. Prefetch. Starting the load the instant the user scrolls a cell into view is too late for the decode.
import { Image } from "expo-image";// Preload around the visible rangefunction prefetchAround(items, index, radius = 6) { const start = Math.max(0, index - radius); const end = Math.min(items.length, index + radius); const urls = items.slice(start, end).map((i) => i.url); Image.prefetch(urls, "memory-disk");}
Call this from onViewableItemsChanged and preload six items on each side as the visible range moves. The radius is a balance with device power; widen it too far and you pressure memory instead. For me, around six on each side was the right compromise between feel and consumption.
One caution: call prefetch too often and you over-fetch and over-decode. When scrolling is fast you need to thin it out — throttle how often you call it. In production, the lower the user's bandwidth, the more this design shows up in reviews.
Tuning you can do while staying on FlatList
Before adding a library, there is tuning on the FlatList side. In order of impact:
Provide getItemLayout
If cell height is fixed, pass getItemLayout. It skips layout math, so scroll start on a long list gets lighter.
Narrow windowSize
windowSize controls how many off-screen cells are kept. The default is generous, so trimming it calms memory on image lists.
Enable removeClippedSubviews
This detaches the rendering of cells that have scrolled off screen. It tends to pay off on image-centric lists, and it is where I try first.
When to move to FlashList
If it still stutters after all the tuning, consider moving from FlatList to @shopify/flash-list. FlashList recycles cells more aggressively, and memory grows more gently with large item counts.
The one thing to watch on migration is estimatedItemSize. Pass a value far from reality and the first scroll gets shakier instead. I recommend measuring a typical cell height on device before passing it. On image lists past a few hundred items, moving to FlashList changes the feel clearly — roughly twice as smooth even on an old phone in my experience.
I will be honest about the migration cost too. FlashList moves over with nearly the same ergonomics as FlatList, but screens that lean on nested scrolling or dynamic heights can briefly break on the way. In my case the safe approach was to move a simple list like the image gallery first and leave the complex screens for later. Rather than replacing everything at once, I personally recommend migrating screen by screen, starting where the payoff is biggest.
Where to start
You do not need all of this at once. Order matters. First stop the re-decode with expo-image and recyclingKey; if that is not enough, add prefetch; and if the item count makes it fundamentally heavy, move to FlashList. Following these stages lets you tell which change earned which gain.
In your own Rork project, start by swapping the built-in Image for expo-image. In most cases the scroll feel changes from that alone. I hope this helps anyone working on the same problem.
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.