RORK LABJP
BUILD — Rork Max runs real Macs in the cloud loaded with Xcode and the iOS SDK, writing SwiftUI, compiling, reading the errors and building again. That loop, not the code generation, is what lifts the outputNATIVE — What comes out is pure Swift and SwiftUI, not React Native. Reaching AR, Metal graphics and widgets that React Native cannot touch is the real gap between this and other buildersPLATFORMS — Coverage spans iPhone, iPad, Apple Watch, Apple TV and Vision Pro, plus iMessage. Worth a look if you want to start from a watch app or an extension rather than a phone screenCOMPANION — The Rork Companion app lets you check a generated build on a real iPhone without a paid Apple Developer account, lowering the bar for trying a first project end to endPRICING — Free to start, paid plans from $25 a month, and Rork Max on the $200 Max plan. Worth working out up front how many projects it takes to earn that backDEADLINE — From August 31, 2026, Google Play requires target API level 36 or higher for new apps and updates alike. Ten days out, and the targetSdkVersion of what you generate is yours to verifyBUILD — Rork Max runs real Macs in the cloud loaded with Xcode and the iOS SDK, writing SwiftUI, compiling, reading the errors and building again. That loop, not the code generation, is what lifts the outputNATIVE — What comes out is pure Swift and SwiftUI, not React Native. Reaching AR, Metal graphics and widgets that React Native cannot touch is the real gap between this and other buildersPLATFORMS — Coverage spans iPhone, iPad, Apple Watch, Apple TV and Vision Pro, plus iMessage. Worth a look if you want to start from a watch app or an extension rather than a phone screenCOMPANION — The Rork Companion app lets you check a generated build on a real iPhone without a paid Apple Developer account, lowering the bar for trying a first project end to endPRICING — Free to start, paid plans from $25 a month, and Rork Max on the $200 Max plan. Worth working out up front how many projects it takes to earn that backDEADLINE — From August 31, 2026, Google Play requires target API level 36 or higher for new apps and updates alike. Ten days out, and the targetSdkVersion of what you generate is yours to verify
Articles/Dev Tools
Dev Tools/2026-08-14Intermediate

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.

Expo SDK 573expo prebuildconfig plugin3React Native227Rork539

I was scrolling through ios/Podfile in one of my wallpaper app repositories when I noticed three lines at the bottom that I had clearly typed myself. I have no memory of adding them.

I read the Expo SDK 57 release notes right afterwards. The headline is the React Native 0.86 bump, but the item that actually changed my week was a single bullet buried under "other highlights": expo prebuild now clears the native directories before regenerating them.

Those three forgotten lines suddenly needed a destination.

The line that matters most in SDK 57

Read the Expo SDK 57 changelog and the framing is honest: this is a deliberately small release. React Native moves from 0.85 to 0.86, and React stays at 19.2, same as SDK 56. The Expo team says they wanted this to be the easiest upgrade you have ever done.

That said, there are 601 commits touching 1,552 files between 0.85.0 and 0.86.0. Even with no breaking changes intended, pulling those in should be a decision you make on purpose — which is exactly why it landed in a separate SDK rather than a patch.

ChangeHow much it affects your day
React Native 0.85 → 0.86 (React stays at 19.2)Android edge-to-edge fixes plus rendering and layout improvements. Good for visual polish
expo prebuild clears and regenerates android / ios by defaultTop priority if you have touched native code. --no-clean restores the old behavior
expo@57.0.9 resolves the Hermes V1 memory regressionEffectively mandatory if your app imports Reanimated or Worklets
Slower startup in development (unresolved)Production builds are unaffected. It only shows up while you work
expo-image gains writeToCacheAsync / readFromCacheAsyncUseful when you want to seed the image cache ahead of time

Do not skim past the third row. The initial SDK 57 release inherited a Hermes V1 regression from SDK 56 that could drastically increase memory usage in apps importing react-native-worklets or react-native-reanimated. expo@57.0.9, published on August 13th, pulls in React Native 0.86.2 and resolves it.

Apps generated by Rork almost always touch the Reanimated family somewhere in their navigation or gesture code. Deciding up front that "if we upgrade, we go to 57.0.9 or later" removed a whole category of confusion for me.

Look at what disappears before you upgrade

I read the --clean default less as a behavior change and more as an honest restatement of a promise that was always there. Continuous Native Generation rests on the idea that your native directories are output, not source. The moment you hand edit them, that promise is already broken.

The painful time to discover it is right after an upgrade. So look first.

Here is the audit I run on a scratch branch: keep a copy of the current directories, regenerate, and read only the delta.

# 1. Work on a throwaway branch
git switch -c chore/sdk57-prebuild-audit
 
# 2. Snapshot the current native dirs (works even if they are gitignored)
cp -R ios ../ios-before-sdk57
cp -R android ../android-before-sdk57
 
# 3. Regenerate on your CURRENT SDK, with the new default behavior
npx expo prebuild --clean
 
# 4. Read only the delta — what survives is your own fingerprint
diff -ru ../ios-before-sdk57/Podfile ios/Podfile
diff -rq ../ios-before-sdk57 ios | grep -v "Pods\|build\|\.xcworkspace"
diff -ru ../android-before-sdk57/app/src/main/AndroidManifest.xml \
         android/app/src/main/AndroidManifest.xml

I filter out Pods and build because both are byproducts of dependency resolution and compilation. Leave them in and the diff runs into the thousands of lines, which buries the three that matter.

Sort whatever comes out into three buckets:

  1. Edits you made by hand — the ones to migrate. This is the real work
  2. Anything autolinking added — regeneration puts it back identically. Leave it alone
  3. Template updates from Expo — the newer version wins after the upgrade, and that is the point

In my case bucket 1 was those three Podfile lines plus a photo library usage description I had typed straight into Info.plist. Both were "I just need this to build right now" edits that should have been config plugins from the start.

Move hand edits into a config plugin

Migrating means you stop writing into files that are designed to vanish, and instead describe the change so it reappears on every regeneration. I started with a single plugins/with-native-tweaks.js at the project root.

// plugins/with-native-tweaks.js
const fs = require('fs');
const path = require('path');
const {
  withInfoPlist,
  withAndroidManifest,
  withDangerousMod,
  AndroidConfig,
} = require('expo/config-plugins');
 
// (1) Declare the Info.plist usage string
const withPhotoLibraryUsage = (config, { message }) =>
  withInfoPlist(config, (config) => {
    config.modResults.NSPhotoLibraryAddUsageDescription = message;
    return config;
  });
 
// (2) Declare an AndroidManifest attribute
const withManifestAttribute = (config, { name, value }) =>
  withAndroidManifest(config, (config) => {
    const app = AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
    app.$[name] = value;
    return config;
  });
 
// (3) Podfile additions are the one case where you must touch the file
const withPodfileSnippet = (config, { snippet }) =>
  withDangerousMod(config, [
    'ios',
    async (config) => {
      const podfile = path.join(config.modRequest.platformProjectRoot, 'Podfile');
      const contents = fs.readFileSync(podfile, 'utf8');
 
      // Guard against double-appending. prebuild runs many times, so be idempotent
      if (contents.includes(snippet.trim())) {
        return config;
      }
 
      fs.writeFileSync(podfile, `${contents}\n${snippet}\n`, 'utf8');
      return config;
    },
  ]);
 
module.exports = (config, props = {}) => {
  config = withPhotoLibraryUsage(config, {
    message: props.photoLibraryMessage ?? 'Used to export saved wallpapers to your photo library.',
  });
  config = withManifestAttribute(config, {
    name: 'android:largeHeap',
    value: 'true',
  });
  config = withPodfileSnippet(config, {
    snippet: props.podfileSnippet ?? '',
  });
  return config;
};

Then wire it up in app.config.ts:

export default {
  name: 'Wallpaper',
  slug: 'wallpaper',
  plugins: [
    [
      './plugins/with-native-tweaks',
      {
        photoLibraryMessage: 'Used to export saved wallpapers to your photo library.',
        podfileSnippet: "post_install do |installer|\n  # project specific post-processing\nend",
      },
    ],
  ],
};

The part that surprised me while writing this was how much the idempotency guard in withDangerousMod actually earns its keep. On paper, since prebuild now wipes first, you always append once to a fresh Podfile. In practice, once you mix in a --no-clean run, or your local flow diverges from what EAS Build does, the same lines can land twice. Any plugin that appends rather than assigns should start by checking whether its work is already done.

withInfoPlist and withAndroidManifest assign, so they never have this problem. Treat withDangerousMod as the last resort and express everything you can declaratively — that turned out to be the safer default, not just the tidier one.

When you are done, run npx expo prebuild --clean once more and diff against your snapshot. If your own fingerprints are gone and only autolinking and template updates remain, the migration is finished.

The order to run the upgrade in

Only after the audit and the migration do I touch the SDK itself. Keeping this order made every failure much easier to attribute.

  1. Align dependenciesnpx expo install expo@^57.0.0 --fix
  2. Verify the version with your own eyes — run npm ls expo and confirm you are on 57.0.9 or later. Stay on the initial release and you inherit the memory regression above
  3. Run the health checknpx expo-doctor@latest
  4. Delete android and ios if they were generated for an older SDK — there is no reason to keep them; the next build regenerates them
  5. Regeneratenpx expo prebuild, and confirm your config plugin took effect
  6. Rebuild your development build — if you use expo-dev-client, an old dev build cannot load an SDK 57 bundle
  7. Check on a real device — comparing memory behavior on a Reanimated-heavy screen before and after is worth the ten minutes

Step 3 is the one people skip. Please do not. expo-doctor tells you about version mismatches before a device build fails and makes you guess.

One more thing worth remembering: the slower development startup is still unresolved as of writing. It is documented as not affecting production. If your dev loop feels heavier after the upgrade, check the known regression before you start suspecting your own code.

When --no-clean is the right call

There is an escape hatch. npx expo prebuild --no-clean applies changes to the existing folders, exactly as before.

I decided not to make it part of my normal workflow, and the reason comes from my own setup.

The wallpaper apps ship as several separate apps from one codebase, with app.config.ts and EAS build profiles swapping the name, icon, and bundle identifier. In that arrangement, holding on to a single ios/ directory does not mean anything — you cannot say which app it belongs to. I wrote up how that configuration is put together in the white-label setup with app.config.ts and EAS.

Where --no-clean genuinely fits is the middle of a migration, on a day when a build has to ship. If the upgrade arrives while hand edits are still sitting in your native directories, it buys you that day. Then you go back and move them into a config plugin. The order changed; the work did not.

Projects generated by Rork give you the code, which means the temptation to fix things by hand is always there. I have given in to it plenty of times, and I do not think that is a failing on its own. What helps is deciding, on the same day you make the edit, whether the file you just touched is source or output.

The step you can take today is not the upgrade itself — it is capturing one prebuild --clean diff. It takes about five minutes, and if nothing shows up, SDK 57 really will be the easiest upgrade you have made.

Thank you for reading. I am still partway through this myself, with the Android side of the diff waiting for me tomorrow.

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-07-28
Counting what prebuild --clean will erase before you upgrade to Expo SDK 57
A raw diff between two generated ios/ trees showed 649 changed lines; only 3 were real edits. How to count what prebuild --clean erases, and move it into a config plugin.
Dev Tools2026-07-30
What Renovate may bump in an Expo app, and what it must never touch
Turning on automated dependency updates in a Rork-generated app also hands Renovate the 123 packages Expo SDK 57 pins. Measured on 2026-07-30, six of them sit a full major version behind npm latest. Here is how to generate the ignore list from the SDK instead of maintaining it by hand.
Dev Tools2026-08-10
Switching to Signed URLs Killed My Image Cache — Decoupling expo-image Keys from the URL
Signed URLs rewrite their query string on every expiry, so a URL-keyed cache never hits. Here is how to derive a stable key and drive expo-image's writeToCacheAsync and readFromCacheAsync yourself, with measured results.
📚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 →