Fixing Image Flicker and Stale Caching with React Native's Built-in Image Component
A list re-renders and every image visibly flickers and reloads, even though nothing about them changed — or the opposite: a user uploads a new avatar and the app keeps showing the old one indefinitely, even after a restart. Both come from the same root cause: the built-in Image component's caching behavior isn't something the app is actually controlling.
The Problem
React Native's built-in <Image> component produces one of two seemingly opposite symptoms. Either images visibly flicker and reload every time a list re-renders or a screen is revisited, even though the same URLs are being requested repeatedly — or the opposite: a user uploads a new avatar or profile photo to a URL that stays the same, and the app keeps showing the old image indefinitely, sometimes surviving even a full app restart. Both look like caching bugs, and both are — just opposite failure modes of the same underlying cause.
Why It Happens
The built-in Image component's caching is platform-default, not explicitly controlled by the app
On iOS specifically, the built-in <Image> component's caching behavior largely defers to the standard NSURLRequest/URLCache policy, which is governed by whatever Cache-Control headers the server actually sends. If the server doesn't send explicit caching headers — or sends ones intended for a different purpose — the platform's own default caching decision may not match what the app actually needs: either caching too aggressively (stale content persists) or not reliably across renders at all (visible flicker on every re-render, since there's no dependable cross-mount cache to fall back on).
The cache key is the URL string, which breaks when content changes at a fixed URL
Many apps intentionally reuse a stable URL for content that legitimately changes over time — a profile photo re-uploaded to the same /avatar/user123.jpg path being the canonical example. Since the cache is keyed on the URL itself, it has no way to know the underlying bytes at that URL changed; it just keeps serving what it already has. This is the exact same class of problem covered for Open Graph images and social platform scraper caches, but showing up here inside the app's own native image cache instead — and it needs the same fundamental fix.
Component remounting can cause visible flicker independent of the actual cache state
A list re-rendering with new component instances — commonly caused by a missing or unstable key prop on list items — can force an <Image> to unmount and remount, visibly re-triggering its load sequence even when the underlying image genuinely is cached at the platform level. Some of the built-in component's caching behavior is tied to the component instance's own request lifecycle rather than a purely global, URL-keyed cache the way a dedicated caching library implements it.
The Fix
1. Version the URL whenever the underlying image content actually changes
// instead of reusing the same URL after a re-upload:
<Image source={{ uri: "https://cdn.example.com/avatar/user123.jpg" }} />
// append a version or timestamp that changes with the content:
<Image source={{ uri: `https://cdn.example.com/avatar/user123.jpg?v=${user.avatarUpdatedAt}` }} />
Since the cache is keyed on the URL string, a genuinely new URL guarantees no stale cached copy is served — this is the direct fix for the "new content, same URL" case, and it doesn't depend on getting any platform's default caching heuristics to behave a particular way.
2. Migrate list-heavy or performance-sensitive image usage to a dedicated caching library
import FastImage from "react-native-fast-image";
<FastImage
source={{ uri: imageUrl, priority: FastImage.priority.normal }}
style={styles.avatar}
/>
Libraries like react-native-fast-image or expo-image implement an explicit, cross-platform-consistent disk-and-memory cache that doesn't depend on each platform's own default HTTP caching behavior — for genuine flicker-on-rerender symptoms (not stale content), this removes the platform inconsistency at its source rather than fighting it.
3. Give list items stable, unique keys so images aren't unnecessarily remounted
<FlatList
data={items}
keyExtractor={(item) => item.id} // stable across re-renders — not the array index
renderItem={({ item }) => <Image source={{ uri: item.imageUrl }} />}
/>
A stable key tied to the item's actual identity (not its position in the array) prevents React from treating a re-render as "a different component" that needs to unmount and remount — removing a source of visible flicker that has nothing to do with the underlying image cache at all.
4. Set explicit Cache-Control headers on the server for genuinely static image assets
location /images/static/ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
For content-addressed or genuinely immutable assets, explicit caching headers let both platforms' default HTTP caching behavior actually reflect intent, rather than leaving the decision to whatever heuristic applies when no explicit policy is specified.
Why This Works
Each fix addresses a distinct point where the app's actual intent and the platform's default caching behavior diverge. Versioning the URL removes the ambiguity of "same URL, different content" at its root, the same way it does for social-platform image caches. A dedicated caching library replaces inconsistent platform defaults with an explicit, predictable cache the app actually controls. Stable list keys remove a flicker source unrelated to caching altogether. And explicit server-side headers let default HTTP caching behave the way the app actually needs instead of guessing.
Conclusion
Both stale images and flickering re-renders with React Native's built-in Image component trace back to the same cause: its caching behavior largely follows each platform's own default HTTP caching policy rather than something the app explicitly controls. Version the URL whenever content genuinely changes at a fixed path, migrate performance-sensitive or list-heavy image usage to a dedicated caching library like FastImage or expo-image, use stable keys on list items so images aren't needlessly remounted, and set explicit Cache-Control headers on the server for assets that are genuinely meant to be cached long-term.
