React Native production lessons from React Universe Meetup x Zalando
Practical React Native patterns for production systems
When React Native becomes part of an existing app, the hardest decisions happen at the boundary. What should stay native? What should move to React Native? What should users see first? And how much work can the device handle before the experience starts to break?
This article pulls four practical lessons from React Universe Meetup x Zalando, held in Berlin in January.
The talks covered:
- Mohit Karekar on performance measurement in Zalando’s brownfield migration work,
- Eugene Verichev on video in feed-heavy React Native interfaces,
- Tejas Kumar on using React Native when a web app needs HealthKit access,
- and Artur Morys-Magiera on practical on-device LLM inference in React Native.
1. Measuring performance in brownfield migrations
In a brownfield migration, the screen can look the same while the system behind it changes completely. That makes performance harder to judge. The user sees one product detail page, but the app may now load, transform, and render data through a different path.
Mohit Karekar showed this through Zalando’s product detail page. The previous implementation relied on:
- A single large GraphQL request.
- Client-side data transformation.
- A complete screen appearing once all data was ready.
The newer React Native implementation used a streaming, module-based model:
- A root API call decides which modules to render.
- The client resolves those modules recursively.
- Multiple requests form a waterfall.
- The screen fills progressively.
Both approaches produced the same UI. TTI alone became less useful because the delivery model changed.


In brownfield work, teams need to measure the handoff between native and React Native, not only the React Native screen itself. Useful checkpoints include:
- Time from native navigation trigger to React Native surface mount.
- Time to first meaningful content.
- Time to interaction readiness.
- Completion time for all modules, where the flow needs it.
- Transition smoothness, including dropped frames.

From load time to Meaningful Render
Zalando handled this with Meaningful Render, a perceived performance metric Mohit compared to Largest Contentful Paint on mobile.
The idea is simple: stop asking when the full screen finishes. Ask when the most important module is ready for the user.
The approach works like this:
- Each screen is built from modules.
- A module can mark itself as meaningful.
- Modules resolve sequentially.
- When the meaningful module lays out, performance is marked complete.
React Native’s onLayout callback gives the signal. Zalando captured that moment through tracing, then inspected Meaningful Render next to root operations, network calls, and rendering work in their observability tooling.
The caveat: rollout data can still get noisy. Small cohorts, outliers, seasonality, and trace issues can distort the signal. Teams need standardized instrumentation and enough context, such as hardware class or session segmentation, before using the metric for rollout decisions.
2. Video in feed-heavy interfaces
Video feeds turn scrolling into resource management. Multiple players can mount at once, stay in memory after leaving the viewport, or compete for decoding resources.
Eugene Verichev focused on two metrics:
- Autoplay time: the time between a video becoming visible and the first rendered frame.
- Jank rate: the share of dropped frames, especially during scroll.
The team first tried the usual React Native fixes: profiling, reducing re-renders or reorganizing state. Those changes helped locally, but they did not remove the feed bottleneck.
Lazy player creation worked better. Instead of creating native players immediately, the app created a player only when a tile reached a visibility threshold, such as 75%. Until then, the tile showed a thumbnail.

The tradeoff worked, but it was not free. Jank dropped from about 25% to about 3%, while time to first frame became almost 2.5x slower. The team had solved the scroll problem, then had to solve autoplay.
Once scrolling was stable, the team improved autoplay through:
- Backend transcoding for entry points
- Short MP4 variants, such as 5-second clips, loaded and cached faster than HLS streams for feed tiles.
- Request prioritization
- GraphQL queries loaded visible content first, then fetched the rest.
- Player pooling
- Native players, codecs, and buffers were reused across instances. On Android, reusing ExoPlayer instances reduced setup overhead.
- Pre-warming
- Players were prepared in advance when the app could infer user intent.
The lesson is sequencing. Stabilize scrolling first, then optimize the interactions.
3. Using React Native when web hits a native API limit
Tejas Kumar’s example was smaller, but the decision was familiar: the app needed HRV data from Apple HealthKit, and the web could not reach it. A full Swift build would have been too heavy for a focused product coming from a web stack.
React Native was the practical middle path:
- It gave the app HealthKit access.
- It kept most development in JavaScript and TypeScript.
- It avoided rewriting the whole product in Swift.
For focused products, React Native is often the practical answer when one native capability blocks the web path, but the team does not want to move the whole product into a native-only workflow.
Expo fits that path well: it gives teams a web-like development model, access to many cross-platform APIs, and in many cases a reason to keep using the same stack for web too.
4. Running large language models on-device
Teams usually look at on-device LLMs for three reasons:
- the feature should work without a reliable network,
- the data should stay on the device,
- backend inference would cost too much at scale.
Artur Morys-Magiera focused on the less tidy part: hardware and runtime behavior.
Modern devices may use CPUs, GPUs, or NPUs. Android adds more variation across chip vendors and SDKs. Abstraction layers such as OpenCL can smooth over some differences, but they can also make performance issues harder to trace.

In one case, the same model initialized in about one second on some devices and 47 seconds on others. The likely issue was not the model, but a memory layout that specific GPU devices handled poorly through OpenCL kernels. Switching to a format with a transposed weights matrix fixed the issue without changing end-user behavior.

Runtime choice also changes the shape of the work. Teams can use TFLite / LiteRT, ONNX Runtime, Executorch, MLC, llama.cpp-based runtimes with GGUF, or OS-provisioned foundation models.
OS-provisioned models deserve a separate decision because they change the runtime, privacy, and platform-support tradeoffs. We covered those constraints in more detail in our other article.
Some runtimes, such as ONNX, are general neural network inference runtimes rather than LLM-specific runtimes. If you use them for LLMs, your application has to own pieces such as tokenization, sampling, and instrumentation.
Closing perspective
The useful pattern across these talks is that React Native production work rarely fails at the framework level. It gets difficult at the edges: the native handoff, the first content users notice, the resources a feed can spend, the API a web app cannot reach, or the runtime behavior of a model on real hardware.
That is where the real engineering work sits. Teams need to know what crosses the native boundary, which moment defines success for the user, and which device or runtime differences can change the result.
React Native is mature enough for that work, but its maturity changes the question. Teams no longer need to ask whether React Native can ship production apps. They need to ask whether their production system is observable, measurable, and predictable enough to improve.

Learn more about React Native
Here's everything we published recently on this topic.






















