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/App Dev
App Dev/2026-05-11Intermediate

Slow Images in My Rork Wallpaper App: Switching to expo-image and What Actually Changed

A hands-on account of switching from React Native's default Image component to expo-image in a Rork-generated wallpaper app — with code examples, blur hash setup, and observable performance improvements.

expo-image5wallpaper app23performance10React Native209image optimization

Wallpaper apps live and die by one thing: how fast images appear on screen. I've been building apps in this category since 2014, and across tens of millions of downloads, the single most consistent complaint in 1-star reviews isn't missing features or crashes — it's "it's slow." Users don't usually explain what's slow. They just leave.

When I built a wallpaper app prototype with Rork, the generated code used React Native's standard <Image> component throughout. The app looked right in the Rork Companion preview. But testing on a physical device told a different story: images were taking 400–600ms to appear, and every scroll produced a flash of white. The images would pop in rather than feel ready.

Rork's generated code wasn't wrong — it was using the correct React Native API. The issue is that React Native's built-in Image component has limitations that don't matter much for simple apps but become very visible once you're loading dozens of high-resolution images in a scroll view.

The fix was switching to expo-image. Here's exactly what I did and what changed.

Why React Native's Default Image Component Causes This

Rork generates standard React Native code, which means <Image> from react-native unless you tell it otherwise. This component is functional, but it has three weaknesses that compound in image-heavy apps:

Weak memory caching. Images scrolled off-screen can be evicted from memory cache and need to re-download when they scroll back into view. In a wallpaper grid, users scroll back to images they liked — and seeing that white flash a second time feels broken.

No progressive display. Nothing is shown until the full image is downloaded. For high-resolution wallpapers on a mobile connection, this creates a visible gap between "list appeared" and "images appeared."

No built-in placeholder support. You can implement a custom loading state, but it requires extra state management and adds complexity that Rork then has to maintain in future edits.

For an app where the core experience is "scroll and look at beautiful images," these limitations directly reduce the perceived quality — even if everything else works perfectly.

What expo-image Does Differently

expo-image is the Expo team's drop-in replacement for the standard Image component, designed specifically to address the limitations above.

The key differences that matter for wallpaper apps:

LRU disk cache. Once an image is downloaded, it's written to disk. On subsequent views — even after the app is closed and reopened — it loads from disk at near-instant speed. This is the biggest single improvement for repeat-scroll behavior.

Blur hash placeholder. While the image downloads, a blurred color preview is rendered immediately from a short hash string. Users see "something" within milliseconds — a warm blur that resolves into the actual image. This changes the perceived experience from "waiting for nothing" to "image appearing."

Smooth crossfade. Instead of an abrupt pop-in, images fade in over a configurable duration when they finish loading. 200ms is enough to feel polished without being noticeable.

Progressive JPEG support. For progressively-encoded JPEGs, the image renders at increasing quality as data arrives.

How to Tell Rork to Use expo-image

For new projects, add this to your prompt before generating the wallpaper list screen:

Use expo-image's Image component for all image display.
Do not use React Native's built-in Image.
Set cachePolicy="memory-disk" on all image components.
Add a placeholder blurhash prop to each image item.

For an existing Rork project, ask it to update:

Replace all instances of <Image source={{ uri: ... }} />
with the expo-image Image component.
Add cachePolicy="memory-disk" and placeholder={{ blurhash: "..." }}
to each one. Keep the same styles.

After Rork generates the updated code, check that expo-image appears in package.json under dependencies. If it doesn't, Rork may have imported from the wrong package — ask it to install expo-image explicitly.

Code Comparison: Before and After

Before — Rork's default output:

import { Image } from 'react-native';
 
function WallpaperItem({ uri }: { uri: string }) {
  return (
    <Image
      source={{ uri }}
      style={{ width: '100%', height: 200 }}
      resizeMode="cover"
    />
  );
}

After — with expo-image:

import { Image } from 'expo-image';
 
// Fallback blur hash: a warm neutral gray
// Replace with per-image blur hashes when available
const FALLBACK_BLURHASH = 'L6PZfSi_.AyE_3t7t7R**0o#DgR4';
 
function WallpaperItem({ uri, blurhash }: { uri: string; blurhash?: string }) {
  return (
    <Image
      source={{ uri }}
      style={{ width: '100%', height: 200 }}
      contentFit="cover"           // same as resizeMode="cover"
      placeholder={{ blurhash: blurhash ?? FALLBACK_BLURHASH }}
      transition={200}             // 200ms crossfade on load
      cachePolicy="memory-disk"    // cache to both memory and disk
    />
  );
}

The API is nearly identical. contentFit maps directly to resizeMode values ("cover", "contain", "fill", etc.), so the style logic doesn't need to change. The migration is mostly a find-and-replace with a few additions — typically under an hour for a full app.

Generating Blur Hashes

Blur hashes are short strings that encode the dominant color structure of an image. expo-image decodes this string on-device in real time to render a blurred color preview. The string itself is around 20–30 characters — small enough to include in API responses without meaningful overhead.

The right place to generate blur hashes is server-side, at the time images are uploaded. Store the hash alongside the image URL in your database, then include it in the API response that your app fetches.

Node.js server-side generation:

import { encode } from 'blurhash';
import sharp from 'sharp';
 
async function getBlurhash(imagePath: string): Promise<string> {
  const { data, info } = await sharp(imagePath)
    .raw()
    .ensureAlpha()
    .resize(32, 32, { fit: 'inside' }) // downscale for faster processing
    .toBuffer({ resolveWithObject: true });
 
  return encode(
    new Uint8ClampedArray(data),
    info.width,
    info.height,
    4, // x components — controls horizontal detail
    4  // y components — controls vertical detail
  );
}

If your wallpaper content is stored in Cloudflare R2 and processed through Cloudflare Workers, you can generate the blur hash in the upload Worker and store it in D1 or KV. This way, every image in your API response already includes its hash, and your app always has something to show immediately.

If you don't have per-image hashes yet, start with a single fallback hash for all images. Even a generic neutral placeholder is better than a white flash. Migrate to per-image hashes incrementally as your upload pipeline is updated.

What Changed After Switching

After migrating to expo-image and testing on a physical device through Rork Companion:

Scroll behavior improved noticeably. Blur hash placeholders appeared immediately as list items rendered — users saw a colored blur instead of a white rectangle. This alone changed the perceived responsiveness significantly.

Scroll-back no longer showed re-loading images. The LRU disk cache kept recently-viewed images available, so scrolling back to something seen 20 seconds earlier felt instant rather than triggering a re-download.

The overall session felt more stable. With fewer white flashes interrupting the visual flow, the app felt like a finished product rather than a prototype.

These are observations from real-device testing, not synthetic benchmarks. But after years of maintaining apps with tens of millions of downloads, I've learned to trust what behavioral metrics and reviews say over what looks right in a simulator. The "it's slow" complaints in app reviews tend to disappear when you address the actual bottlenecks — and image loading is one of the most common.

A Note on AdMob Integration

If your wallpaper app uses AdMob interstitial or rewarded ads, faster image loading has a secondary benefit: users spend more time scrolling before they're likely to close the app. Since AdMob impression revenue correlates directly with session depth, reducing friction in the core browsing experience is also an indirect revenue improvement. This is something I've seen repeatedly with the apps I've built — the relationship between UX quality and ad revenue is more direct than it might seem.

Related Articles

For overall app performance beyond images, the Rork App Performance Optimization Guide covers rendering, memory, and bundle size in detail.

If your wallpaper list uses FlatList and performance is still not where you want it, Migrating from FlatList to FlashList in Rork is a natural next step.

If you're starting a wallpaper app from scratch, Building and Publishing a Wallpaper App with Rork covers the full process from idea to App Store.


Start by replacing one <Image> component with expo-image, set a fallback blur hash, and test it on a physical device. The difference in perceived speed is usually visible within a minute. Once you see it, migrating the rest of the app becomes easy to justify.

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

App Dev2026-07-14
Long-Press Context Menus for a Gallery Item in a Rork Expo App
Long-pressing a wallpaper card does nothing, yet iOS users expect a preview and a menu. From why Pressable alone falls short, to a native context menu with zeego, resolving the scroll-vs-long-press conflict, wiring up save and share, and a custom overlay fallback for Android — all with working code.
App Dev2026-06-23
Why Wallpapers Look Dull on Device: Taming Display P3 in the Delivery Pipeline
The same wallpaper looked dull once set on device. The culprit was a mix-up between wide-gamut Display P3 and sRGB. Beyond embedding profiles, here is how to tell whether the pixels are truly wide-gamut, a pre-delivery gate script, and the Android wide-color story, across six wallpaper apps.
App Dev2026-07-07
Laying Out Variable-Height Images in Two Columns: A Masonry Wallpaper Gallery in a Rork Expo App
From why numColumns cannot pack variable-aspect images cleanly, to a dependency-free column-balancing algorithm, to keeping virtualization with FlashList masonry and a pragmatic no-dependency fallback, building a wallpaper gallery with real code.
📚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 →