react-brain
Browse decisions
React & language foundations 3App architecture 11UI 13Platform & native 9Build, test, observe, secure 6AI in React apps 3stack composerdecision recordscensusstaleness benchnewsletterarchitecturechangelogmethodologyroadmap

entriesplatform-native · verified 2026-09-24 · react-native

Brownfield integration & micro-frontends

reviewedconfidence: lowthis tier holds 64% on the public scorecard (7/14 graded · 0 overturned) →

related decisions: ota

cited by: ota

re-verified 7× — 2026-09-24 · 2026-09-10 · 2026-08-18 · 2026-07-28 · 2026-07-16 · 2026-07-10 +1 · changelog

recommendation

A scale-stage concern. Adding RN screens to an existing iOS/Android (or .NET MAUI) app → react-native-brownfield (5.1+ for iOS preloadBundle), or Expo brownfield when the RN side is an Expo project and you want the first-party path. Independently deployable screens / super-apps → Granite or Rock. The Expo SDK version decides the react-native-brownfield major.

  • add RN screens to an existing native app → react-native-brownfield (5.1+)
  • Expo project embedded in a native app, first-party tooling preferred → Expo brownfield (expo-brownfield; integrated or isolated)
  • Expo SDK 55 app embedding RN → react-native-brownfield 4.x (5.0 drops SDK 55; upgrade the brownfield major with the SDK)
  • slow first open of RN screens in a brownfield iOS app → react-native-brownfield preloadBundle (5.1.0+)
  • native host is a .NET MAUI app → react-native-brownfield (its C# binding over the same native binding)
  • native host and RN screens share state → Brownie (@callstack/brownie, same version as react-native-brownfield)
  • many teams shipping independently deployable screen bundles from a CDN → Granite (Toss)
  • super-app with micro-frontends and native build caching → Rock (Callstack; 0.x)
  • deciding brownfield vs a greenfield rewrite → checkpointed greenfield with a real brownfield fallback (choose by where the risk sits: continuity vs owning a runtime boundary; see the Callstack delivery-question reading)

Low confidence — fast-moving or lightly-vetted domain: treat the pick as a vetted lead and prototype before committing.

Options & tradeoffs

the field considered — and why each one isn’t the default here

optiontradeoff
react-native-brownfield (Callstack)Embeds RN in an existing iOS, Android or .NET MAUI app; iOS init via ReactNativeBrownfield.shared.startReactNative() over RCTReactNativeFactory; brownfield-cli prebuilt XCFrameworks cut hybrid iOS compiles ~39% (Callstack, 2026-07). 5.1.0 (2026-09-03, verified vs release) adds iOS preloadBundle; 5.x drops Expo SDK 55. See note: STARTUP, VERSION FLOOR.
Brownie (@callstack/brownie)Shares native↔JS state for brownfield; generates Swift + Kotlin types from .brownie.ts; iOS + Android. Released from the react-native-brownfield repo in lockstep with it since 4.0.0 (2026-07-06): 5.1.x, verified vs npm.
Granite (Toss)Micro-frontend: each screen an independently deployable ~200KB bundle on CDN (ESBuild). @granite-js/react-native 1.0.45 (2026-09-16, verified vs npm).
Rock (Callstack)Super-apps via micro-frontends + native build caching. rock 0.15.2 (2026-09-16, verified vs npm; callstackincubator/rock), still 0.x.
Expo brownfield (first-party)SDK 55+: official Expo path for adding Expo/RN to an existing native app, integrated or isolated (AAR/XCFramework artifacts) (docs.expo.dev/brownfield); the first-party alternative to the Callstack tooling, with its own expo-brownfield CLI. expo-brownfield 57.0.23 (2026-09-24, verified vs npm).

Verified notes

THE PICTURE: brownfield is a scale-stage concern: embedding RN into native apps or splitting a super-app. The tooling is mostly Callstack's (react-native-brownfield, Brownie, Rock) and Toss's (Granite), plus, since SDK 55, a first-party Expo path. react-native-brownfield is the default because it covers iOS, Android and .NET MAUI hosts over one native binding; the question that picks the major is which Expo SDK the embedded app is on. STARTUP (2026-09-10, verified vs the 5.1.0 GitHub release): without preloadBundle, iOS builds the RCTReactNativeFactory only and defers bundle evaluation to the first RN view, so the first screen pays for it; with preloadBundle (5.1.0, 2026-09-03) the React Host starts and the JS bundle evaluates in parallel with the rest of app launch (Android's ReactNativeBrownfield.initialize already did this) → it is the first thing to try when RN screens are blamed for a slow first open. Timing change: onBundleLoaded now runs on the MAIN thread, not the JS thread. VERSION FLOOR (2026-07-28, verified vs the GitHub release): @callstack/react-native-brownfield 5.0.0 (2026-07-23) has exactly one breaking change: it DROPS Expo SDK 55 support (brownfield-cli bumps to 5.0.0 with it) → SDK-55 apps stay on 4.x; pair the brownfield major with the Expo SDK upgrade, not before it. LOCKSTEP (2026-09-24, verified vs npm): @callstack/brownie, @callstack/brownfield-cli and @callstack/react-native-brownfield share version numbers and release dates from 4.0.0 (2026-07-06) on; Brownie's earlier 3.x line ended at 3.15.0 (2026-07-02) → pin all three to the same version. EXPO UPDATES (2026-07-10, verified vs the Callstack article body): Expo Updates works in an ISOLATED brownfield architecture with SDK 55; OTA for embedded RN screens was previously a known limitation. Because 5.x drops SDK 55, this is a react-native-brownfield 4.x fact. RB-E-OTA owns the OTA thread. MAUI HOST: react-native-brownfield ships a .NET MAUI variant, a C# binding project (XCFramework on iOS, AAR on Android) over the same native binding as the Swift/Obj-C and Kotlin/Java paths; the cost is mostly NuGet coexisting with npm/CocoaPods/Gradle (see reading). HISTORY: 2026-09-24 CORRECTED: `npx brownfield package:ios` was credited to the Expo path; the `brownfield` bin belongs to @callstack/brownfield-cli (Expo's CLI is expo-brownfield). 2026-09-24 CORRECTED: Brownie was pinned "stable v3.13.x (2026-06)"; it moved to lockstep with react-native-brownfield at 4.0.0 (2026-07-06) and is on 5.1.x. 2026-09-10 REOPENED: react-native-brownfield 5.1.0 preloadBundle; .NET MAUI host variant + reading reconciled from harvest/2026-09-03. 2026-07-28 REOPENED: 5.0.0 drops Expo SDK 55. 2026-07-10 REOPENED: capability flip, Expo Updates in isolated brownfield on SDK 55.

Canonical reading

Editorial annotations on why each piece matters — the articles themselves are the originals; read them there.

React Native in .NET MAUI: A Practical Brownfield Integration GuideJuozas Petkelis, Marcin Szalski, Artur Morys-Magiera, Hur Ali (Callstack)

The MAUI half of react-native-brownfield, which the entry's other rows do not cover: a .NET binding project wrapping the same native binding the iOS/Android paths use (XCFramework on iOS, AAR on Android), surfaced as idiomatic C#. Walks the part that actually costs time — making NuGet coexist with npm/CocoaPods/Gradle — then wiring MauiProgram.cs / AppDelegate / MainActivity lifecycle events, and handing navigation back and forth between MAUI's Shell and React Native. Relevant when the existing app is MAUI, which is common where the org already standardized on C#.

Migration to React Native in 2026 Starts With a Delivery QuestionMichał Pierzchala (Callstack)

The decision that comes BEFORE this entry's tooling: brownfield or greenfield. The framing is that the trigger for migrating is usually delivery cost (two implementation tracks, two review paths, two QA cycles, two release trains), and that the choice between paths is really a choice of where the risk sits during the transition. Brownfield keeps the native apps as the production host and moves one flow at a time — it preserves installed-app continuity (session state, secure storage, push identity, subscriptions, app extensions) and leaves a native fallback, at the cost of owning a runtime boundary (packaging, host integration, native↔RN navigation, shared state) while maintaining two surfaces at once. Greenfield builds a replacement and ships it under the same bundle ID — less architectural drag and a codebase agents work better in, but continuity becomes the risk: auth, deep links, push tokens, app groups, persisted data and analytics all have to survive the cutover, and mature products fail on accumulated behavior rather than on screens. The third option is the useful one: start greenfield with a fixed checkpoint and a REAL brownfield fallback, where the checkpoint tests product continuity (authenticated flows, analytics, accessibility, performance, at least one meaningful native boundary) and not just how fast screens get rebuilt.

Rebuilding the Doctolib Homepage from Webview to NativeDoctolib

Production case study migrating a 115M-hits/month homepage from WebView to native RN with a BFF-driven section architecture (~50% TTI improvement, measurable conversion gains). Durable brownfield-integration lessons.

Unlocking Expo Updates in an Isolated Brownfield Architecture with SDK 55Callstack

The capability-flip walkthrough behind this entry's note: running EAS/Expo Updates inside an isolated brownfield embed (XCFramework packaging via `npx brownfield package:ios`), which had been a hard limitation of embedded-RN architectures. Read when you need OTA on brownfield screens.

React Native production lessons from React Universe Meetup x ZalandoCallstack

Four hard-won production patterns — brownfield-handoff metrics (Meaningful Render), video-feed jank sequencing, native API bridging, on-device LLM hardware variance. Real-world scale-stage RN guidance.

Sources

Depth (in-domain rules) is owned by the engineering-principles skill — this entry is selection breadth.

The full explanation

The reviewed long-form essay behind this entry — the why, not a how-to. Also on GitHub.

About brownfield integration & micro-frontends

Diataxis: Explanation. This page builds understanding of the brownfield decision — why embedding React Native in an existing native app is a boundary problem, and how the candidate tools divide that boundary between them. It is not an embedding how-to and not an XCFramework walkthrough. The candidate list and one-line tradeoffs live in the index entry RB-E-BROWNFIELD; depth defers to the engineering-principles skill. Read this for the why.

The one idea that organises everything: the boundary is the product

In a greenfield RN app the framework owns the whole screen and the interesting work is inside it. In a brownfield app the interesting work is the seam: a contract between the host native app and the embedded RN surface. The entry's candidates barely overlap — because each one owns a different clause of that contract:

  • Init. Who starts React Native, and how self-contained that start is. react-native-brownfield's whole pitch is a self-contained iOS init — ReactNativeBrownfield.shared.startReactNative(), wrapping RN core's RCTReactNativeFactory. Since 5.1.0 (2026-09-03) that init takes preloadBundle: the React Host starts and the JS bundle evaluates in parallel with app launch instead of on the first RN view (Android's initialize already did this), and onBundleLoaded now runs on the main thread. Its brownfield-cli packages the embed as prebuilt XCFrameworks (npx brownfield package:ios); Expo's first-party path answers the same clause with its own expo-brownfield tooling, integrated or isolated (AAR/XCFramework artifacts).
  • State. What crosses the seam while both sides run. Brownie (@callstack/brownie) shares native↔JS state for brownfield and generates Swift + Kotlin types from .brownie.ts — the contract made literal, as generated types on both sides (iOS + Android; released in lockstep with react-native-brownfield since 4.0.0, 2026-07-06, now 5.1.x).
  • Deployment granularity. How much ships independently. Granite (Toss) makes each screen an independently deployable ~200KB bundle on a CDN (ESBuild); Rock (Callstack) targets super-apps via micro-frontends plus native build caching.
  • Updates. Whether the embedded surface can change without the host shipping. The entry's capability flip (2026-07-10, verified against the Callstack article body): Expo Updates now works in an isolated brownfield architecture with SDK 55 — OTA for embedded RN screens was previously a known limitation, and "that limitation is a thing of the past."

The libraries differ, in other words, by how much of the contract they own: one owns init, one owns the state clause, two own deployment shape, and the Expo path — the first-party alternative to the Callstack tooling, since SDK 55 — owns packaging and, now, updates. (The entry grounds these four clauses; other clauses a real seam has — threading, navigation handoff — it does not cover, though its Zalando reading shows the handoff being measured, via a Meaningful Render metric.)

The default, and why

A scale-stage concern: embed RN into a native app with react-native-brownfield (+ ReactNativeFactory on iOS). For independently-deployable screens / super-apps, look at Granite or Rock.

"Scale-stage" is the load-bearing phrase, and the note repeats it: this is tooling you reach for when a native app — and usually an organisation around it — already exists. The two when-clauses split cleanly along the contract: adding RN screens to an existing native app is the init clause → react-native-brownfield; many teams shipping independent bundles is the deployment clause → Granite / Rock. The landscape is mostly Callstack/Toss tooling (Rock is still 0.x) plus, since SDK 55, the first-party Expo path.

One version fact decides the react-native-brownfield major: 5.0.0 (2026-07-23) has exactly one breaking change, it drops Expo SDK 55. SDK-55 apps stay on 4.x, so upgrade the brownfield major together with the Expo SDK, not before it. The Expo Updates capability flip below is an SDK 55, and therefore 4.x, fact. The same library also covers a .NET MAUI host through a C# binding project over the same native binding.

The landscape, facet by facet

react-native-brownfield (Callstack) — the embed itself: RN inside an existing native app, with the self-contained iOS init (startReactNative() over RCTReactNativeFactory). The recommendation's named default for the add-RN-screens case. Its brownfield-cli (npx brownfield package:ios) prebuilds XCFrameworks, which cuts hybrid iOS compile times ~39% (Callstack, 2026-07), and preloadBundle (5.1.0+) is the first thing to try when RN screens are blamed for a slow first open.

Expo brownfield (first-party, SDK 55+) — the official Expo path for adding Expo/RN to an existing native app (docs.expo.dev/brownfield), integrated or isolated (AAR/XCFramework artifacts), with its own expo-brownfield CLI. The isolated architecture is where Expo Updates was unlocked for embedded screens on SDK 55 (Callstack's walkthrough packages with npx brownfield package:ios, which is Callstack's brownfield-cli, not Expo's).

Brownie (@callstack/brownie) — the state seam: native↔JS state sharing with Swift + Kotlin types generated from .brownie.ts, iOS + Android. Versioned in lockstep with react-native-brownfield and brownfield-cli since 4.0.0 (2026-07-06); pin all three to the same version.

Granite (Toss) — micro-frontends: each screen an independently deployable ~200KB CDN bundle, built with ESBuild.

Rock (Callstack) — super-apps via micro-frontends plus native build caching.

The production evidence lives in the entry's reading list: Doctolib rebuilding a 115M-hits/month homepage from WebView to native RN with a BFF-driven section architecture (~50% TTI improvement, measurable conversion gains), and the Zalando meetup lessons — brownfield-handoff metrics (Meaningful Render), video-feed jank sequencing, native API bridging, on-device LLM hardware variance.

Tradeoffs and failure modes to name out loud

  • Reaching for this at the wrong stage. The entry says it twice — a scale-stage concern. A greenfield app has no host to contract with; this shelf is for apps that already exist.
  • Assuming OTA can't reach embedded screens. Stale since SDK 55: the capability flip is the entry's own headline note, and it points at RB-E-OTA for that side of the story.
  • Treating the landscape as uniformly stable. Rock is still 0.x (0.15.2), and the Callstack packages move a major at a time with the Expo SDK. Verify the maturity of any specific tool before committing.
  • Paying full hybrid compile cost. The prebuilt-XCFramework flow exists partly because rebuilding RN inside a native app is expensive — ~39% faster hybrid iOS compiles is the measured win; Rock's native build caching is the same concern at super-app scale.
  • Micro-frontends without the org that needs them. Granite/Rock's when-clause is "many teams shipping independent bundles" — a team-shape condition. One team with one release train is the other when-clause.
  • An unmeasured handoff. The Zalando reading's first lesson is a metric — Meaningful Render — for the brownfield handoff; the seam is a product surface, so it needs its own measurement.

How it interacts with the rest of the stack

  • OTA (RB-E-OTA). The entry's own cross-reference: Expo Updates inside an isolated brownfield embed is the intersection of the two entries — which surface can change without a host release is now a live question on both.
  • Native integration (RB-E-NATIVE). The init clause is built directly on RN core's native surface (RCTReactNativeFactory); the embed sits inside the platform-native layer that entry owns.
  • Depth (engineering-principles). No specialist skill owns brownfield depth here; the defer is to generic architecture and code-quality judgment.

In one paragraph

Embedding React Native in an existing native app makes the boundary the product: a contract between host and embed, whose clauses the candidate tools divide between them. react-native-brownfield owns init (self-contained ReactNativeBrownfield.shared.startReactNative() over RN core's RCTReactNativeFactory); Brownie owns state (native↔JS sharing with Swift + Kotlin types generated from .brownie.ts); Granite and Rock own deployment shape (independently deployable ~200KB CDN bundles per screen; super-apps with native build caching); and Expo's first-party path (SDK 55+, expo-brownfield) owns packaging, integrated or isolated. The isolated architecture unlocked Expo Updates in brownfield embeds on SDK 55 (previously a known limitation; see RB-E-OTA), and Callstack's brownfield-cli prebuilt XCFrameworks cut hybrid iOS compile times ~39%. It is a scale-stage concern in a landscape that is mostly Callstack/Toss tooling (Rock still 0.x): add RN screens → react-native-brownfield (4.x on SDK 55, 5.x after); many teams shipping independent bundles → Granite or Rock; verify maturity before committing.


See also: RB-E-OTA (Expo Updates in isolated brownfield embeds — the entry's capability flip), RB-E-NATIVE (the platform-native surface the init clause builds on). Depth: the engineering-principles skill. Background reading: Doctolib's WebView→native homepage rebuild, Callstack on Expo Updates in isolated brownfield (SDK 55), and the Zalando production lessons (Meaningful Render and friends).

Related in platform-native: native · media · storage · native-ui · keyboard · payments · games · alt-frameworks