RORK LABJP
PRICE — Rork Max spans $200 to $1,800 per month, with the upper tiers aimed at heavier builders and teamsFREE — The free tier lands at roughly five prompts per week, enough to try it but not to build on continuouslySHIP — App Store publishing is automated through builds, certificates, and submission, so you can ship an iOS app without a Mac or XcodeSIM — A browser-streamed simulator lets you watch your app run in a real Apple environment from your own browserNATIVE — It reaches HealthKit, ARKit and LiDAR, NFC, Dynamic Island, and Metal 3D — territory React Native cannot touchFUNDING — Rork raised a $15M seed led by Left Lane Capital, announced April 9, 2026, and acquired app builder PaperlinePRICE — Rork Max spans $200 to $1,800 per month, with the upper tiers aimed at heavier builders and teamsFREE — The free tier lands at roughly five prompts per week, enough to try it but not to build on continuouslySHIP — App Store publishing is automated through builds, certificates, and submission, so you can ship an iOS app without a Mac or XcodeSIM — A browser-streamed simulator lets you watch your app run in a real Apple environment from your own browserNATIVE — It reaches HealthKit, ARKit and LiDAR, NFC, Dynamic Island, and Metal 3D — territory React Native cannot touchFUNDING — Rork raised a $15M seed led by Left Lane Capital, announced April 9, 2026, and acquired app builder Paperline
Articles/Dev Tools
Dev Tools/2026-04-24Intermediate

When Your Rork App Binary Tops 150MB: 5 Causes to Isolate and a Step-by-Step Slim-Down Plan

My Rork-built app archived at 180MB and App Store Connect stopped me cold. Here is how I broke the bloat into 5 measurable causes and got it down to 69MB — including the three cuts that backfired.

rork58bundle-sizeoptimization5app-store15expo11

Have you ever uploaded a Rork-generated app to TestFlight and been met with an App Store Connect warning that the binary is too large? I hit that exact wall while rebuilding one of my own wallpaper and wellness apps in Rork. The very first archive came in close to 180MB, and I had to get serious about slimming it down before shipping.

Here's the thing I wish I'd known sooner: most Rork-generated apps can be shrunk by 50–60% without rewriting a single line of feature code. What follows is the exact path I took, along with the numbers I measured at each step with xcrun and the Xcode Organizer.

Start by Pinpointing Where the Fat Lives

Without measuring, you'll "feel lighter" after each change and quit before the real wins. A typical Rork-generated app's binary can be broken into five chunks:

  • The JavaScript bundle (main.jsbundle or Hermes bytecode)
  • Images, fonts, and videos shipped as assets
  • Native library binaries from Expo modules
  • Localized resources (translation files and per-locale assets)
  • Debug symbols (dSYM and unstripped native code)

Here's how the split looked before and after on one of my wallpaper apps. The numbers come from the Xcode Organizer's App Size Report and xcrun measurements.

ComponentBeforeAfterMain lever
Image assets84 MB21 MBWebP conversion, dropping unused resolutions
JavaScript bundle12 MB8 MBmoment→dayjs, per-method lodash imports
Native libraries48 MB34 MBRemoving unused Expo modules, ABI splits
Localized resources6 MB4 MBDeleting unused locales
Debug symbols22 MB2 MBSeparating dSYM from the upload, stripping
Total172 MB69 MB (-60%)

What the table really shows is how unevenly the headroom is distributed. Images gave up 63 MB; localized resources, just 2 MB. Fix the single heaviest component first and the same effort pays off very differently.

On iOS, open Xcode → Organizer → Archives → Download App Size Report to see the post-App-Thinning download size split by cause. On Android, inspect output-metadata.json from ./gradlew :app:bundleRelease, or run APK Analyzer for a per-module breakdown. The goal is to replace "it feels big" with a sentence like "images take up 72MB", because only then can you build a reduction plan.

Re-Encode Assets with the Right Format

In roughly 7 out of 10 bloated Rork apps I've worked on, the largest culprit is image assets. Rork generates high-quality placeholder imagery that fits your prompt, which is convenient — but bundled as PNG, and especially for wallpaper or wellness apps, you'll blow past 100MB in a heartbeat.

Here's the three-step routine I run on every new project:

# 1. Batch-convert to WebP (keep SVG and transparency-critical PNGs untouched)
# brew install webp to get cwebp
find assets/images -type f \( -name "*.png" -o -name "*.jpg" \) -print0 \
  | xargs -0 -I {} cwebp -q 82 "{}" -o "{}.webp"
 
# 2. Compare original vs WebP sizes
du -sh assets/images/**/*.png assets/images/**/*.webp | sort -h | tail -20
 
# 3. Delete originals and update references in one sweep
find assets/images -type f \( -name "*.png" -o -name "*.jpg" \) -delete
rg -l "\.png\"" src/ | xargs sed -i '' 's/\.png"/\.webp"/g'

On the app I was slimming down, image assets dropped from 84MB to 21MB — a 75% cut. React Native supports WebP natively, but flattening transparent PNGs can occasionally break rendering on Android, so keep icon assets that rely on transparency in their original format as a safety net.

If you want to go further on image format choices, I've written a companion piece on the decode cost of WebP vs AVIF and CDN content negotiation.

Make the JavaScript Bundle Explain Itself

If images are handled and the binary is still heavy, the next suspect is a fat dependency that shouldn't be there. Since Expo SDK 55, running npx expo export --dump-sourcemap gives you a sourcemap you can feed into react-native-bundle-visualizer for a per-module treemap.

# Install and analyze
npm install -D react-native-bundle-visualizer
npx react-native-bundle-visualizer --platform ios --dev false

This usually surfaces the usual suspects: lodash imported wholesale (70KB+), or moment with its timezone data weighing in above 400KB. Swapping lodash for lodash-es with named imports, and moment for date-fns or dayjs, can shave 500KB combined without touching product code.

One Rork-specific gotcha: freshly generated projects sometimes ship with @babel/preset-env in babel.config.js even though Expo's babel-preset-expo already handles everything. Removing the redundant preset alone trims tens of kilobytes from the release bundle.

Tighten the Native Layer with Hermes and ProGuard

On iOS, confirm Hermes is enabled (it's the default in Expo SDK 55 and above). On Android, make sure both enableProguardInReleaseBuilds = true and enableShrinkResourcesInReleaseBuilds = true are set in android/app/build.gradle.

// android/app/build.gradle
android {
    buildTypes {
        release {
            shrinkResources true
            minifyEnabled true
            proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
        }
    }
}

For Android, turning on ABI splits lets you ship an arm64-v8a-only APK, cutting the user-downloaded size roughly in half. If you're using Google Play, simply uploading an App Bundle (AAB) lets Play handle device-specific slicing for you. ABI splits matter most when you're distributing APKs directly, such as through an enterprise channel.

Use App Thinning and On-Demand Resources to Lower Real Download Size

If "total app size" in App Store Connect is still painful after the steps above, the next lever is App Thinning on iOS. Rork tends to emit flat asset files, but if your app — say, a wallpaper app — wants to deliver different resolutions for different devices, register those images in Assets.xcassets with 1x / 2x / 3x slots. The end user will then only download the set matching their device.

On-Demand Resources go one step further: they let you ship a minimal first-boot binary and fetch the rest of your resources later, such as during a tutorial. The payoff is huge for learning and gaming apps, but the integration cost is meaningful. For most Rork apps I'd start with plain App Thinning and reserve ODR for when it clearly pays off.

For the performance angle adjacent to size, see practical techniques for cutting Rork app startup time. Size and startup are a pair — tackle them together and the user-visible improvement compounds.

Measure Binary Size and OTA Update Size as Separate Things

Everything above is about shrinking the binary that ships on first install. Once you're in operations, a second size metric starts to matter: the size of the diff delivered over-the-air.

Expo SDK 55's EAS Update switched to shipping only the Hermes bytecode diff. In my apps, updates that used to re-ship the entire multi-megabyte JS bundle on every feature change now send just the delta, and the update payload dropped by close to 75%. Smaller transfers mean the update applies almost instantly in the background, and your EAS plan's bandwidth budget stretches further.

The catch: slimming the binary does not automatically slim your OTA updates. Bundle a new large image with every release and the diff balloons anyway. Optimize the two separately — the first-install binary with Asset Catalog and App Thinning, and ongoing updates by keeping the JS-and-asset diff small. Expo's fingerprint tool can decide mechanically whether a change is OTA-safe or needs a full native build.

Three Cuts That Came Back to Bite Me

The tricky part about shrinking a binary is that the damage shows up late. The build succeeds, review passes, and the problem only surfaces after release. Here are three I walked into personally.

shrinkResources deletes resources you reference dynamically. If any code builds a resource name as a string and looks it up with getIdentifier(), Gradle sees no reference and strips it. The build is green; the app crashes the moment that screen opens. That's how the category icons in my wallpaper app disappeared. Declare what must survive:

<!-- android/app/src/main/res/raw/keep.xml -->
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@drawable/category_*,@raw/onboarding_*"
    tools:shrinkMode="strict" />

Adding tools:shrinkMode="strict" makes stripping the default, which sounds aggressive but actually helps: anything you forget to declare fails loudly and early instead of six weeks later.

Dropping dSYMs makes crash logs unreadable. The 22 MB → 2 MB row in the table above comes from here, but deleting the symbol files turns every Crashlytics stack trace into a column of hex addresses. The fix isn't to remove them — it's to separate them from the upload and archive them yourself.

# Pull dSYMs out of the archive, keep them, and strip the shipped binary
cp -R MyApp.xcarchive/dSYMs "$HOME/dsyms/$(date +%Y%m%d)-$(git rev-parse --short HEAD)"
# Upload to Crashlytics from the archived copy
./Pods/FirebaseCrashlytics/upload-symbols -gsp GoogleService-Info.plist -p ios "$HOME/dsyms/..."

Naming those folders with the commit hash saved me from the least rewarding investigation in mobile development: figuring out which build a given dSYM belongs to.

Images in the Asset Catalog can't be replaced over the air. Moving images into Assets.xcassets is what makes App Thinning work, but those assets now live on the native side of the fence — EAS Update can't touch them. I moved seasonal artwork in there, then discovered the next swap required a full binary submission.

The line I draw now is simple. Anything fixed for the life of the app — icons, logos, permanent backgrounds — goes in the Asset Catalog. Anything you might swap during operations stays in the bundle, or better yet moves to a CDN. Drawing that line once pays back every release after.

Verify the Win in Delivered Size, Not File Size

One last step before you call it done. Looking at the size of your local .ipa is reassuring but misleading, because users download only the thinned slice for their specific device. A 120 MB artifact on your Mac is routinely 58 MB on the phone.

The Xcode Organizer's App Size Report is authoritative for iOS, but when you want a number before submitting, the export already ships one:

# Read the size report produced by an Ad Hoc / App Store export
cat build/export/App\ Thinning\ Size\ Report.txt | grep -A3 "App size"

On Android, generate the real per-device APKs from the bundle and measure those:

# List delivered size across device configurations
bundletool build-apks --bundle=app-release.aab --output=app.apks \
  --ks=release.keystore --ks-key-alias=release
bundletool get-size total --apks=app.apks --dimensions=ABI,SDK

get-size total reports the minimum and maximum download size per configuration. I save that output before and after each optimization pass. Having the numbers on record turns the next round from guesswork into arithmetic.

Where to Put Tomorrow's Effort

Trying to halve a binary in one pass usually stalls out. The faster path is sequential 20–40% cuts: measure, then images, then the JavaScript bundle, then the native layer. Keeping that order matters because it lets you attribute each gain to a specific change instead of a vague bundle of edits.

If you do exactly one thing tomorrow, open the Xcode Organizer's App Size Report and identify your single heaviest component. If images lead, a WebP pass can move hundreds of megabytes. If the native layer leads, ABI splits and an AAB come first. Working heaviest-first is the whole trick.

A smaller binary does more than silence review warnings. Users on slow connections stop abandoning the install halfway through — and that first download is the first cost anyone pays to try your app. Lowering it is a quiet courtesy to someone you'll never meet.

When the numbers move, write the delta down. It becomes the most reliable estimate you'll have the next time you do this.

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

Dev Tools2026-05-23
Rork-Specific 'expo start --offline forbidden': Four Causes in Rork's Template Config
When expo start --offline returns 'forbidden' specifically on a Rork-generated project, the cause is usually Rork template config: tsconfigPaths, an un-generated expo-router cache, native prebuild, or a lockfile mismatch. Four Rork-specific fixes; the generic Expo proxy and dependency-validation guide is covered separately.
Dev Tools2026-05-20
Fixing 0x8badf00d Watchdog Kills That Wipe Out Rork Apps at Launch
Your Rork iOS app dies right after launch on real devices. Crashlytics shows exception code 0x8badf00d. Here is the watchdog termination story and the exact steps an indie developer running React Native apps for 50M downloads uses to make it stop.
Dev Tools2026-05-05
Getting Your Rork Max App Through App Store Review: A Practical Guide
A complete guide to App Store submission for Rork Max apps—covering the most common rejection reasons, metadata requirements, privacy policy setup, pre-submission testing, and post-launch ASO for continued growth.
📚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 →