●ACQUISITION — Rork makes its first acquisition, buying Paperline, a macOS app that generates native Swift apps with AI●FUNDING — The $15M seed led by Left Lane Capital backs Rork's push to redefine how mobile apps are built and monetized●GROWTH — Rork Max reportedly hit $1.5M ARR within three days of launch and doubled annual revenue in two weeks●ENGINE — Rork Max runs on Claude Code and Claude Opus 4.6, the first web Swift builder aiming to replace Xcode●SPLIT — Standard Rork uses React Native (Expo); Rork Max generates native Swift across the whole Apple ecosystem●PRICING — Start free; paid plans begin at $25/month, with Rork Max at $200/month●ACQUISITION — Rork makes its first acquisition, buying Paperline, a macOS app that generates native Swift apps with AI●FUNDING — The $15M seed led by Left Lane Capital backs Rork's push to redefine how mobile apps are built and monetized●GROWTH — Rork Max reportedly hit $1.5M ARR within three days of launch and doubled annual revenue in two weeks●ENGINE — Rork Max runs on Claude Code and Claude Opus 4.6, the first web Swift builder aiming to replace Xcode●SPLIT — Standard Rork uses React Native (Expo); Rork Max generates native Swift across the whole Apple ecosystem●PRICING — Start free; paid plans begin at $25/month, with Rork Max at $200/month
Keeping Your Rork App's Expo SDK Upgradable Year After Year — Release Trains and Regression Safety Nets
A maintenance design for keeping Rork-generated Expo apps running through yearly Expo SDK upgrades without breakage. Covers release-train cadence, dependency pinning, regression safety nets, and splitting OTA from store delivery, with real examples.
Keeping Your Rork App's Expo SDK Upgradable Year After Year
Late last year I tried to jump one of my neglected indie apps two SDK generations at once, and it cost me half a day. The expo-notifications API had quietly changed, react-native-reanimated version alignment broke, and the build passed while notifications silently stopped arriving on a real device. I spent the day isolating the cause and shipped nothing.
What it taught me is that an Expo SDK upgrade is not a "someday spring cleaning." It is an annual inspection that belongs in your operating rhythm. Rork generates apps on top of Expo (React Native). They run beautifully on day one, but six months later the SDK has moved to a new major, and the gap you ignored quietly compounds.
This article lays out a maintenance design for running that yearly upgrade calmly and continuously. I am sharing the pinning policy, steps, and safety nets I actually use to operate my own apps.
Why Batching Upgrades Always Hurts
Jumping two generations at once multiplies the changes. The SDK core, Reanimated, Gesture Handler, and various native modules each carry their own breaking changes, and they all move at the same time.
The larger the diff, the wider the area you must search when something fails. With one major at a time you can trace "which module broke" linearly. With two majors at once, the combinatorial explosion makes the root cause hard to pin down.
My conclusion is simple: upgrade small, regularly, on a fixed rhythm. The mechanism that makes this real is the release train.
The Release Train: Treat Upgrades Like a Train Timetable
A release train treats an upgrade not as an individual decision but as a train that departs at a fixed time. Anything that misses the train waits for the next departure; you do not cram it into today's run.
I run a three-week sprint with the following rhythm.
Internal distribution, device checks, store submission
Ship with minimal production impact
The key rule is to raise at most one SDK major per departure. Two departures a year means two majors; even one departure keeps you one major behind at worst. That alone structurally avoids the "two generations at once" nightmare.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦A release-train design that lets you climb one or two Expo SDK majors per year without breakage, mapped onto a concrete three-week sprint
✦Pinning policy for package.json, the expo install --check workflow, and a breaking-change triage table — the exact rules I run on my own indie apps
✦OTA versus store delivery split, Sentry release tracking, and pre-decided rollback thresholds for upgrade day, all in one place
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Pinning Policy: What to Pin and What to Leave to Expo
If you leave the package.json that Rork emits untouched, your library versions drift away from what the SDK expects. The guiding axis here is: leave what Expo guarantees to Expo, and explicitly pin everything else.
Concretely, install Expo-managed packages through expo install so they snap to the range the SDK requires. Conversely, pin third-party packages you added yourself (analytics SDKs, billing SDKs) to exact versions.
Two points. First, let Expo-managed expo-* packages and companions like react-native-reanimated follow the SDK's recommended range with a tilde. Second, lock packages that hit revenue or incident detection when they break — billing and monitoring — to exact versions, so you control when they move.
Verify alignment mechanically every time.
# Detect drift from versions the SDK requires (no changes)npx expo install --check# Auto-fix detected drift to SDK recommendationsnpx expo install --fix
Wiring --check into CI lets you catch the moment someone (or an AI) carelessly adds a non-recommended version, before it merges.
Triaging Breaking Changes: Read Only What Bites
Major-upgrade changelogs are often enormous. Reading all of it equally burns time, so I read only the APIs my app actually touches.
My steps:
Mechanically extract the list of modules the current code actually imports
From the SDK upgrade guide, pull only the breaking changes that match that list
Sort the pulled changes into "fix now," "verify behavior," and "ignorable"
Extracting imports fits in a one-liner.
# List actually-used expo-* and react-native-* modulesgrep -rhoE "from '(expo[-/][^']+|react-native[-/][^']+)'" src/ \ | sed "s/from '//;s/'//" | sort -u
Cross-referencing only these modules against the changelog roughly halves what you read. Since switching to this "read only the APIs you use" habit, my survey week has felt noticeably lighter.
Regression Safety Net: So You Don't Learn It Broke in Production
The notification trouble above is exactly the danger: the build passes while behavior quietly breaks. Stopping that requires a smoke test that exercises the launch path and core features every time.
You do not need a heavy suite. The app launches, the main screens render, and billing restore and notification registration do not throw. Confirming this minimum every time stops most fatal injuries in advance.
// __tests__/smoke.test.ts — always run right after an upgradeimport { render, screen } from '@testing-library/react-native';import App from '../App';describe('upgrade smoke', () => { it('renders the app root without throwing', () => { render(<App />); expect(screen.getByTestId('app-root')).toBeTruthy(); }); it('billing module does not throw on init', async () => { const Purchases = require('react-native-purchases').default; await expect( Purchases.configure({ apiKey: 'test_key' }) ).resolves.not.toThrow(); });});
Bugs that only appear on real hardware (notifications, camera, real billing flows) go onto a manual checklist run against an internal build. As an indie developer, I make a point of physically exercising notification delivery, purchase restore, and deep links on a device for every upgrade departure.
Splitting OTA from Store Delivery: Ship SDK Updates Through the Store
Get this wrong and you cause a production incident. EAS Update (OTA) can only deliver JavaScript and assets; a major SDK upgrade that includes the native layer must never go out over OTA. The runtime mismatches, and you scatter launch crashes.
Split by runtime version.
Type of change
Delivery path
Reason
Copy, minor UI, logic fixes
EAS Update (OTA)
JS only. Delivered instantly
Expo SDK major / native dependency added
Store delivery (EAS Build)
Runtime changes. OTA not allowed
Emergency logic rollback
EAS Update (OTA)
Roll back to the previous channel instantly
In eas.json, separate channels and cut a new runtime version on SDK updates.
Deliver OTA only within "the same runtime as the native build already on the store." When you raise the SDK, always ship through the store. Holding that single line dramatically reduces upgrade-induced crashes.
Upgrade-Day Safety Net and Rollback Conditions
Finally, the safety net to hold on departure day. I do not push the upgraded version to 100% at once; I release it small via the store's staged rollout and watch crash rates per release in Sentry.
Decide rollback conditions as numbers in advance. A vague "let's watch it" is the most dangerous setting.
// Tie per-release crash tracking to Sentryimport * as Sentry from '@sentry/react-native';Sentry.init({ dsn: 'YOUR_SENTRY_DSN', release: 'app@2.4.0+sdk52', // include the SDK version in the release name tracesSampleRate: 0.2,});
The rollback conditions I use:
If the staged rollout's crash-free rate drops one point or more versus the previous version, halt distribution immediately
If real users hit errors in notifications or billing, roll back via OTA when the cause is logic
When isolated to a native cause, request a store rollback to the previous build
Embedding the SDK version in the release name lets you see at a glance, in Sentry, "which SDK increased what." As someone with limited monitoring time on indie apps, this is one of the highest-leverage habits I have. Because revenue paths like AdMob impressions and purchase restore matter most, that mechanical threshold pays off there first.
A First Step You Can Take Now
If your SDK is currently stuck several generations behind, do not leap straight to the latest. Cut a branch that raises just one major on the next departure, and start by making the current drift visible with npx expo install --check.
Once an upgrade becomes a mechanism, each round costs surprisingly little. Since switching to a release train, I no longer burn half a day batching everything at year's end. I hope this serves as a blueprint for the regular inspection of anyone else running an app solo over the long haul.
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.