RORK LABJP
DEADLINE — Four days remain until Google Play requires Android 16 (API level 36). From August 31 it applies to new apps and to updates of existing ones alikeRULES — The submission rule and the visibility rule are separate. An app you have stopped updating still disappears for new users on newer devices if it targets below API 35EXTENSION — An extension keeps you shipping to all users until November 1, but the form lives in Play Console and has to be filed before the deadline passesEXPO — Expo SDK 57 moves React Native from 0.85 to 0.86 while React stays at 19.2, and 0.86 is intended to land without breaking changesHERMES — 57.0.9 updates React Native to 0.86.2 and clears the Hermes V1 memory regression from SDK 56, which shows up in apps importing reanimated or workletsPREBUILD — expo prebuild now clears and regenerates the native android and ios directories by default, so hand-edited native changes vanish unless you audit for them firstDEADLINE — Four days remain until Google Play requires Android 16 (API level 36). From August 31 it applies to new apps and to updates of existing ones alikeRULES — The submission rule and the visibility rule are separate. An app you have stopped updating still disappears for new users on newer devices if it targets below API 35EXTENSION — An extension keeps you shipping to all users until November 1, but the form lives in Play Console and has to be filed before the deadline passesEXPO — Expo SDK 57 moves React Native from 0.85 to 0.86 while React stays at 19.2, and 0.86 is intended to land without breaking changesHERMES — 57.0.9 updates React Native to 0.86.2 and clears the Hermes V1 memory regression from SDK 56, which shows up in apps importing reanimated or workletsPREBUILD — expo prebuild now clears and regenerates the native android and ios directories by default, so hand-edited native changes vanish unless you audit for them first
Articles/Dev Tools
Dev Tools/2026-08-27Intermediate

Count your worklets before you start deleting your own code

Hermes V1 attaches 512KB of debug metadata to every worklet, so simply importing Reanimated can add tens of megabytes. Here are the three numbers to count before blaming your own code, and how to choose between the four available fixes.

Hermes8React Native Reanimatedreact-native-workletsExpo SDK 574Memory5

I spent an evening with one of my wallpaper apps tethered to Xcode, watching the memory gauge. Right after launch it sat a few dozen megabytes higher than I expected. Not high enough to crash, but the line was clearly not where it used to be.

My first suspect was my own code. Image cache ceilings, prefetch counts, list cell recycling. I trimmed each one, measured again, and the line barely moved.

The cause was outside anything I had written.

What grew was debug metadata, not animation state

Hermes V1 is not a polished version of legacy Hermes. It is a rebuilt engine with a different way of evaluating code at runtime, and it carries extra metadata that is genuinely useful when debugging.

react-native-reanimated uses react-native-worklets underneath so that animation math can run off the JS thread. In the traditional approach, now called Legacy Eval Mode, the animation function is sent to a second JavaScript runtime as a string and evaluated there, directly on the UI thread.

That means production builds go through an eval-shaped path. And Hermes V1 was attaching its debug metadata to exactly that path.

It is worth pausing on why nobody caught this earlier. Most engines assume eval in production is rare, and that when it appears it is there to sandbox untrusted code — not to drive the frame-by-frame behaviour of a user interface. Under that assumption, spending half a megabyte to make evaluated code debuggable is a reasonable trade. Reanimated's architecture quietly falls outside it, and the two reasonable decisions collided.

According to the investigation published by Software Mansion, who maintain Reanimated, the cost is 512KB per unique worklet. Their write-up notes that Reanimated and Worklets evaluate more than 100 worklets just to initialise, which is at least 50MB at app startup. They also checked the Expensify bundle and found over 1,000 unique worklets, which could exceed 0.5GB if every one of them were evaluated — though evaluation is lazy, so a real user would have to visit every screen in a single session to get anywhere near that.

The image cache I had been trimming was two orders of magnitude away from the actual problem.

Three numbers to count before you suspect yourself

Get the order wrong and you spend the evening I spent. There are three numbers worth counting first.

1. Are worklets even in your dependency tree?

npm ls react-native-reanimated react-native-worklets

Projects generated by Rork almost always touch both, because screen transitions and gestures are built on them. A transitive dependency counts here just as much as a direct import.

2. How many worklets did you write yourself?

grep -rhoE "['\"]worklet['\"];" src | wc -l
grep -rcE "['\"]worklet['\"];" src

The first line gives you a total, the second a per-file breakdown. Treat both as a lower bound. The Worklets Babel plugin automatically workletises callbacks passed to useAnimatedStyle or Gesture even when you never typed the directive, so this only catches the ones you wrote by hand.

3. What order of magnitude should you expect?

python3 -c "n=3; base=100; print(f'{n+base} worklets -> about {(n+base)*512/1024:.0f} MB')"
# 103 worklets -> about 52 MB

Put your own count in n and the library's initialisation footprint — roughly 100 — in base. If the number that comes out matches the order of magnitude you are seeing on the device, the thing to fix is not in your code.

That calculation is where I stopped searching. There was nothing left on my side worth deleting.

Four fixes, and the question that picks one

The Hermes-side fix already exists. What differs is how you take delivery of it.

RouteWhat you doWhen it fits
Upgrade ExpoMove to expo@57.0.9 or later, which brings React Native 0.86.2You are already on SDK 57 and can move. The shortest path
Enable Bundle ModeSwitch Worklets over to Bundle ModeYou also want the newer Worklets features. Requires Metro-side setup
Bytecode optionUse the experimental hermesBytecode option in the Babel pluginYou cannot touch Metro and cannot bump React Native
Pin HermesPin the 0.15 line, where the fix was backportedYou want Worklets' internal behaviour left exactly as it is

Bundle Mode works by exposing the whole bytecode bundle to the secondary runtimes instead of shipping individual code strings. Because Hermes loads bytecode with mmap and does it lazily, the extra cost is close to nothing. Software Mansion recommends it on performance grounds regardless of this particular regression.

There is a fifth option: wait for React Native 0.87 upstream. If you pick it, be honest with yourself that you are shipping those 50MB to users for the entire waiting period.

Which of the four you pick comes down to a single question: what do you least want to move? If the answer is your native configuration, upgrading Expo is uncomfortable and pinning Hermes is easy. If the answer is your build pipeline, the Babel option barely touches anything. If the answer is your dependency versions, Bundle Mode leaves them alone. There is no route that changes nothing, so the useful decision is choosing which part of the project stays still.

I chose the upgrade, and cleared the blockers first

I went with upgrading Expo. The reason is unglamorous: any route that changes Worklets configuration leaves behind a setting that somebody — quite possibly future me — will find in six months and no longer understand. A version bump explains itself in the commit history.

There is a catch. SDK 57 changed the default behaviour of expo prebuild, which now clears ios and android before regenerating them. If you have hand-edited native configuration, the upgrade itself hands you a second problem. I wrote that part up separately in Find the native edits expo prebuild will erase before you upgrade to SDK 57, and it is worth walking through before you bump anything.

After the upgrade, two checks are enough.

npm ls expo react-native
# expo@57.0.9 or later / react-native@0.86.2 or later
npx expo-doctor@latest

Then open the same screens on a real device, in the same order as before, and watch the memory. A different path through the app is not a comparison.

That last point sounds obvious and is the one I get wrong most often. Because worklet evaluation is lazy, memory climbs as screens are visited for the first time in a session. Open three animated screens before the upgrade and five after it, and you will read the difference as a partial fix when it may be a complete one, or the reverse. I now write the screen order down before I measure and follow the list on both runs.

On Android the equivalent read is the memory profiler in Android Studio, and the numbers will not match iOS. That is fine — you are comparing each platform against its own earlier self, not against the other platform.

Counting first changes what you decide afterwards

What I took away from this had less to do with Hermes than with sequence. When memory grows, the first move is not to start deleting your own code. It is to estimate the order of magnitude of the increase. If the magnitudes do not line up, the cause lives in a different layer.

If you are staring at unexplained memory in an app that uses Reanimated right now, run npm ls expo react-native before anything else. That one command decides where your next few hours go.

Designing the memory ceiling itself — staged release under pressure, and reading terminations that never show up as crashes — is a separate topic I covered in memory pressure handling for Rork iOS apps. It reads better once the runtime-level cause is already off the table.

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 $15 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-27
Two Months of Rork × Hermes in Production — Cold Start and Memory in Real Numbers
What actually happens to cold start, memory, and crash rates after running Rork-generated apps with Hermes enabled in production for two months. Field notes from an indie developer with 50 million cumulative downloads.
Dev Tools2026-08-14
Find the native edits expo prebuild will erase before you upgrade to SDK 57
Expo SDK 57 makes expo prebuild clear and regenerate ios and android by default. Here is how to audit your hand edits first, move them into config plugins, and why 57.0.9 matters for Reanimated apps.
Dev Tools2026-08-09
Measuring Hermes Bytecode Delta Updates: One Added Module Cost 29x More Than One Changed Line
I measured OTA delta sizes against a 2.4MB Hermes bytecode bundle. Changing one line cost 2,104 bytes; adding one module cost 61,197. The widely repeated advice about stable module IDs turned out to matter least.
📚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 →