Skip to content

ZALANDO 2025-10-02

Read original ↗

Zalando — Accelerating Mobile App Development at Zalando with Rendering Engine and React Native

Summary

Zalando is migrating its mobile app (previously two separate codebases — a native iOS app and a native Android app, 90+ screens total) to React Native, reusing the existing web Rendering Engine framework as the application-layer foundation. The post canonicalises three architectural decisions: (1) React Native as a package — a brownfield-integration architecture that packages the RN runtime into an npm Entry Point consumed by both a standalone Developer App (greenfield RN environment for web engineers unfamiliar with Xcode/Android Studio) and a native Framework SDK embedded in the legacy app; (2) cross-platform UI via react-strict-dom + StyleX — writing an HTML-subset that Metro maps to React Native primitives on mobile and to plain HTML/CSS on web, with escape hatches for platform-specific implementations through Foo.native.ts file resolution and react-strict-dom's compat API; (3) Turbo Module + dependency-injection contract — a three-language API contract (TypeScript + Swift + Kotlin) that lets the RN layer and the legacy native app communicate (e.g. wishlist badge increment) while preserving isolation. Zalando has migrated several screens including Discovery Feed, the new front screen featured in Q2 2025 results, proving the architecture handles media-heavy content at consumer-facing scale (52M+ customers).

It is the wiki's first canonical instance of brownfield React Native integration into an existing native codebase at consumer-app scale, a contrast-pair to Shopify's five-year RN retrospective (greenfield-friendly, team-by-team adoption) and the Shopify new-architecture migration (sources/2025-09-12-shopify-migrating-to-react-natives-new-architecture, about dual-architecture compat across a mature RN codebase). Zalando's posture is different: a still-native-first app adding RN screen by screen, with the Rendering Engine reused across web and mobile as the shared application-layer framework.

It is also the first wiki source that stretches Rendering Engine across web + mobile (axis 7 extension — previously only web). The same Renderer abstraction (supercharged React components with built-in observability, data-fetching, state, and analytics) now powers Zalando's cross-platform customer-experience unification across the website and the mobile apps, with react-strict-dom + StyleX supplying the UI substrate.

Key takeaways

  1. Three pillars driving the RN bet: build-and-ship faster, progressive adoption, include web. Zalando's RN decision is motivated by three explicit requirements (quoted verbatim from the post):
  2. "Build & Ship faster" — experiment with new customer experiences for 52M+ fashion, beauty, and lifestyle customers; need features buildable on all platforms with minimal effort.
  3. "Progressive adoption" — rebuilding the entire app at once is out of scope; migrating 90+ screens at once is not an option; technology choice must be adoptable screen by screen.
  4. "Including Web" — website and apps had diverged technically; must preserve and build upon web-era capabilities (backend-steered UI, composable components) across platforms.

These are the forcing functions behind the architecture. Each maps to a specific design decision: RN reduces per-platform build cost; React Native as a package enables per-screen adoption; react-strict-dom + Rendering Engine carries web-era investment forward.

  1. React Native as a package is a brownfield-integration architecture, not a greenfield RN app. Zalando started with a proof-of-concept app built as a standard greenfield RN app (react-navigation, react-native-reanimated, react-native-video, custom turbo modules). Proving technical viability wasn't enough; to ship, they had to embed RN into the existing large native codebase. Three problems surfaced:
  2. Native-dependency conflicts — RN or community packages pulling native packages in different versions than the main app used.
  3. No clear separation — git submodules and similar approaches don't enforce strict separation between the RN code and the legacy app code.
  4. Bad developer experience — building the full native app is slow despite caches; requiring every RN contributor to build the full iOS/Android app (including setting up Xcode / Android Studio) is a friction barrier for web engineers who are expected to contribute to RN.

The solution — React Native as a package — packages the React Root Component + initialisation logic into an npm package called the "Entry Point". Two consumers: - The standalone Developer App — consumes the Entry Point with a standard RN environment. Web engineers get "all the benefits of React Native, as in any greenfield app, with full isolation from the legacy architecture." Custom dev menu on top of RN's default (accessed via shake-device gesture) lets developers switch between JS bundles (released versions, PR builds, local builds) and other dev-experience utilities. - The native Framework SDK — a native library (iOS + Android) containing the full RN stack behind a simple-to-use interface: ReactNativeViewFactory with initialize() and loadView(deepLinkProps, launchOptions) on iOS; ReactNativeViewFactory with initialize(), createViewHostedInActivity(activity, screenParameters), and createViewHostedInFragment(fragment, screenParameters) on Android. The legacy app consumes the SDK the same way it consumes any other native framework.

The post explicitly credits callstack/react-native-brownfield as the open-source package that generalises this work.

  1. Turbo Module + dependency-injection contract is the legacy-app interop mechanism. Some features span the RN-native boundary — the post's example: adding a product to the wishlist from RN must increment the badge displayed by the legacy native app. The contract flow:
  2. Define a turbo module with TypeScript interface — e.g. Spec extends TurboModule declaring addProduct(sku, shouldShowNotification?): Promise<void> and onProductChange: EventEmitter<ProductChangeEvent>.
  3. Define a compatible native protocol (Swift + Kotlin) — e.g. WishlistProtocol with matching methods + callbacks — plus an injection point (WishlistConfig.delegate: TurboWishlistProtocol?) where the legacy app registers its implementation.
  4. Legacy app implements the protocol and injects itself into the Framework SDK at startup.
  5. Standalone Developer App implements the same protocol with mocked versions, so wishlist-dependent features can be tested without the full native app.

The key property: "clear boundaries and contracts". This is a three-language API-first workflow — the TypeScript type, Swift protocol, and Kotlin interface must all align before implementation begins. Zalando explicitly calls out the consequence: "when combining three environments into one (TypeScript, Swift and Kotlin) it's crucial to first properly define these API contracts and ensure that all involved environments are compatible with this contract as early as possible. Otherwise, you run into challenges where the API design might not be feasible on all platforms, requiring you to undo work that has already been done."

  1. Cross-platform UI: react-strict-dom + StyleX, chosen over react-native-web. Two candidate approaches for cross-platform UI on top of RN:
  2. react-native-web — write normal RN components (<View />, <ScrollView />, <Text />, etc.) and let the library translate them to HTML for the web.
  3. react-strict-dom — write an HTML subset (html.div, html.button, etc.) and let the library map HTML to RN primitives on mobile, to plain HTML on web.

Zalando chose react-strict-dom for two reasons: - Future-proof language choice. HTML and CSS have evolved over decades and are unlikely to be displaced. RN-specific abstractions "could potentially become obsolete at any point." - Zero runtime cost on web. react-strict-dom's build step removes the abstraction layers on web; the output is plain HTML + CSS with no adapter overhead.

Zalando pairs react-strict-dom with StyleX (Meta's CSS library) for styling. StyleX works hand-in-hand with react-strict-dom to support theming via tokens (font sizes, colours, borders) that behave like CSS variables across all platforms and are transformed to regular CSS on web.

  1. Metro's platform-specific imports are the per-platform escape hatch. When a component's behaviour must diverge between platforms, Zalando uses Metro's file-extension-based resolution:
  2. Foo.ts — default (for web, when no platform-specific variant exists).
  3. Foo.native.ts — both iOS and Android RN platforms.
  4. Foo.ios.ts / Foo.android.ts — specific platform if iOS and Android need to diverge further.
  5. A separate types file — so multiple implementations can have safe type-checking against a single contract.

When a consumer writes import "./Foo", Metro picks the correct file based on target. The consumer doesn't know or care which implementation it got.

  1. react-strict-dom's compat API is the extend-or-override escape hatch. Even with HTML mapping, sometimes the underlying native component needs extra props. react-strict-dom provides a compat.native API that lets the developer inject specific props into the mapped native component:
<compat.native
  {...props}
  aria-label="label"
  as="span"
>
  {(nativeProps) => <Text {...nativeProps} />}
</compat.native>

This is the ejection point when the HTML subset doesn't cover a platform-specific requirement — the developer hand-codes the native component and passes props through.

  1. The cross-platform code share is a balancing act, not a maximum. Zalando explicitly rejects the "maximise code share" framing: "Writing cross-platform code is a balancing act between saving development time and limiting yourself to cross-platform constraints. It's important to accept that having 100% code shared between all platforms or even between iOS and Android, is not the goal, just like writing everything in JavaScript and avoiding native code is not the goal, and that's totally fine." This is the same architectural posture Shopify canonicalised in sources/2025-01-13-shopify-five-years-of-react-native-at-shopify (the "think native and React Native, not native or React Native" rule). Zalando arrives at it independently for the cross-platform UI axis (HTML vs native) on top of the mobile-vs-web share axis — see patterns/mixed-native-plus-cross-platform-mobile-stack.

  2. Launch early on a simple, low-traffic screen. Zalando's first migrated screen was "a low-traffic and very simple screen". Quoted rationale: "even in this simplest scenario, we learned a lot. It provided opportunities not just to test the technology early without breaking a major feature, but also to build proper observability based on real customer experience." This is a de-risking pattern — the purpose of the first RN screen is not to deliver business value but to exercise the full pipeline (dev-app loop, deploy, crash reporting, analytics, performance observation) against real production traffic at low blast radius. The follow-up evidence: Zalando has since migrated Discovery Feed, the new front screen featured in Q2 2025 results — a media-heavy, high-traffic surface that validates the architecture at load and scope.

Systems mentioned

  • React Native — chosen cross-platform framework.
  • Rendering Engine — Zalando's in-house React application framework; the "supercharged React components with observability, metrics, traces, data fetching, caching, state management, and analytics built in" primitive, now reused on mobile. Stated as having allowed a production-ready RN prototype in "just a few weeks" by integrating the web-era framework into RN.
  • Interface Framework (IF) — the web-era framework Rendering Engine is part of.
  • react-strict-dom — Facebook's library that maps HTML-subset components to React Native primitives on mobile and plain HTML on web.
  • StyleX — Meta's CSS library for cross-platform theming.
  • Metro — RN's JS bundler; supplies platform-specific file resolution (.native.ts, .ios.ts, .android.ts).
  • react-navigation — used in the proof-of-concept.
  • react-native-reanimated — used for animation in the proof-of-concept.
  • react-native-video — used for video playback in the proof-of-concept.
  • callstack/react-native-brownfield — open-source package that generalises the "RN as a package" pattern, produced by Zalando's partners at Callstack.
  • Zalando Mobile Framework SDK — native library wrapping the RN stack behind a simple interface, consumed by the legacy native app.
  • Zalando Mobile Developer App — standalone greenfield RN app, used for RN development without the legacy-app build cost.
  • Discovery Feed — the media-heavy front screen migrated to RN; featured in Q2 2025 results.
  • Zalando Design System tokens — cross-platform styling variables (colours, font sizes, borders) consumed via StyleX.

Concepts surfaced (new to wiki)

Concepts surfaced (existing, reused)

  • concepts/shared-mobile-foundations — Zalando's shared observability / metrics / tracking / state management / design system layer, now reused across web + iOS + Android (via Rendering Engine's Renderer abstraction).
  • concepts/cross-platform-client-library — the whole-app-framework altitude canonicalised by Shopify is here restated as Zalando's RN-with-Rendering-Engine bet.
  • concepts/micro-frontends — the architectural ancestor in Zalando's frontend lineage; Rendering Engine (and its Renderers) is the successor framework to Project Mosaic.

Patterns surfaced

New:

Existing, reused:

Numbers / scale

  • 52M+ fashion, beauty, and lifestyle customers — stated as the scale the RN bet must serve.
  • 90+ screens — size of the existing native app surface that cannot be rebuilt in one pass.
  • "Just a few weeks" — time from integrating the web-era Rendering Engine into the RN proof-of-concept to a production-ready setup with live data access.
  • Two (iOS + Android) — number of separate codebases
  • architectures the migration replaces.
  • Three (TypeScript + Swift + Kotlin) — language axes the API contract must satisfy.
  • Discovery Feed — named load-bearing migrated screen, featured in Zalando's Q2 2025 financial results.

Caveats / gaps

  • No P50/P75 render-time or crash-free-session numbers. Unlike Shopify's <500ms P75 / >99.9% crash-free disclosure, Zalando does not publish screen-load or stability metrics for the migrated screens.
  • No old-vs-new-architecture posture stated. Post does not clarify whether the Framework SDK uses RN's New Architecture (Fabric + TurboModules) exclusively or straddles both. Given the explicit use of "turbo module" terminology, New Architecture is implied but not confirmed.
  • Dev-experience timing numbers absent. No disclosure of how long a Developer App cold build takes vs the full legacy-app build, even though developer-experience was one of the three forcing functions.
  • Migration progress not quantified. "A few screens, ranging from major to minor" is the only disclosure of how far the migration has progressed against 90+ screens.
  • Team-structure implications not discussed. The post doesn't describe how Zalando is staffing the RN effort, whether there's a dedicated platform team building the Framework SDK / Developer App separate from feature teams consuming it. Compare to Shopify's explicit rotating framework-upgrade team in sources/2025-09-12-shopify-migrating-to-react-natives-new-architecture.
  • No interop call-volume or latency numbers. Wishlist- example interop flow is described architecturally; the frequency of RN↔native calls and their latency overhead are not disclosed.
  • No discussion of code-push / OTA updates. The Developer App is shown switching between JS bundles (released versions, PR builds, local builds), but the post does not describe whether production end-users receive RN JS-bundle updates out-of-band from native app releases, or whether each RN update ships as a native binary update.
  • No crash-handling-across-RN-native-boundary discussion. If a JS crash occurs in RN, does the legacy app continue, crash, or show a fallback? Not stated.
  • Rendering Engine's mobile adaptation unspecified. The post says Rendering Engine integrated into RN "in just a few weeks" but does not walk through which RE concepts (tile, .withQueries, .withProcessDependencies, .withRender) map unchanged vs which were adapted to the mobile runtime.

Contrast pairs on the wiki

Source

Related

Last updated · 507 distilled / 1,218 read