PATTERN Cited by 1 source
RN as consumable npm Entry Point¶
Problem¶
You want to integrate React Native into an existing large native iOS/Android app without:
- Coupling the RN code to the legacy app's source tree.
- Forcing every RN contributor to build the full legacy app to iterate.
- Leaking RN's native dependencies into the legacy app's dependency graph in a way that triggers version conflicts.
Naïve approaches (git submodules, direct-source integration) fail on all three. See concepts/brownfield-rn-integration for the full failure analysis.
Pattern¶
Package the React Root component + RN initialisation logic into a dedicated npm package — call it the Entry Point. Design the Entry Point so it has two independent consumers:
- Greenfield Developer App (a standalone RN app) — see patterns/standalone-developer-app-for-rn.
- Native Framework SDK (iOS + Android) — a native library
that exposes RN functionality behind a simple
native-API-shaped interface. E.g.
ReactNativeViewFactory.loadView(deepLinkProps, launchOptions)on iOS.
The legacy native app links the Framework SDK; it does not link the Entry Point directly, and it does not link RN source at all.
Entry Point (npm)
│
├── Developer App (greenfield RN)
│
└── Framework SDK (native iOS/Android library)
│
└── Legacy Native App (consumes like any framework)
Why this works¶
- Dep resolution happens in one place — the Entry Point's
package.json. Native deps that RN or RN community packages pull in are resolved inside the Entry Point's boundary; conflicts with the legacy app's deps are contained. - Separation is enforced by the package boundary. The legacy app has no way to accidentally import RN internals — only the Framework SDK's public API surface is reachable.
- Developer experience is restored via the Developer App. Contributors iterate against a standard RN environment; the legacy native app build is out of their loop.
- Dual consumers force clean API design. The Entry Point can't rely on legacy-app-specific context because the Developer App doesn't have one. This forces dependencies on legacy-app state to be expressed through Turbo Module + DI contracts (see patterns/turbo-module-plus-di-contract-for-native-interop).
Canonical implementation¶
@callstack/react-native-brownfield
is the open-source package that generalises this pattern,
produced by Zalando's partners at Callstack.
Canonical production case¶
Zalando's [[sources/2025-10-02-zalando-accelerating-mobile-app-development-with-rendering-engine-and-react-native|2025-10-02 post]] describes building out this architecture end to end:
- Entry Point npm package with React Root + init logic.
- Developer App with custom dev menu supporting JS-bundle switching (released, PR, local).
- Framework SDK exposing iOS
ReactNativeViewFactory.loadViewand AndroidReactNativeViewFactory.createViewHostedIn*. - Legacy Zalando mobile apps (iOS + Android) link the SDK and progressively mount RN-backed screens.
The validation evidence: Zalando successfully migrated Discovery Feed — the new media-heavy front screen — and several other screens using this architecture.
When to reach for this pattern¶
- You have an existing large native app (90+ screens in Zalando's case) that you can't rewrite.
- You want RN to be progressively adoptable screen-by-screen rather than big-bang.
- Your RN contributors include web engineers whose productivity is hurt by requiring full native-app familiarity.
When not to¶
- You're building RN-first from scratch (greenfield). Don't pay the package-boundary cost for no return; just ship a standard RN app.
- You only need a single RN screen. The packaging overhead outweighs the benefit for small integrations.
Seen in¶
- sources/2025-10-02-zalando-accelerating-mobile-app-development-with-rendering-engine-and-react-native — canonical wiki first production case.