Skip to content
Published on

Cross-Platform Mobile Development 2026 Deep Dive — React Native, Flutter, Expo, Capacitor, Tauri 2, Kotlin Multiplatform, Compose MP, .NET MAUI, NativeScript, Lynx

Authors

Prologue — In 2026, the "write once, run both" promise enters a new phase

When React Native first appeared in 2015, the promise "write once, run on both iOS and Android" was huge. Facebook built it, you wrote JSX, and a JavaScript bridge manipulated native views. It was a promise, but also a limit. The bridge was asynchronous, serialization was expensive, and keeping a smooth 60fps was always something to fight for.

In May 2026, the landscape has shifted.

  • React Native 0.76 (Meta, released October 2024, now in 2026 operations) ships the New Architecture (Fabric renderer plus TurboModules plus JSI) as the default for new apps. Bridge serialization is gone, and synchronous native calls are possible.
  • Flutter 3.27 (Google, released late 2024, in 2026 operations) made the Impeller renderer the default on both iOS and Android. Dynamic Skia shader compilation is gone, and the first-frame jank has almost disappeared thanks to precompiled shaders.
  • Tauri 2.0 (Tauri team, October 2024) officially supports iOS and Android mobile targets. "Rust on mobile" finally became realistic.
  • JetBrains Kotlin Multiplatform 2.1 and Compose Multiplatform 1.7 pushed shared iOS UI into stable.
  • ByteDance Lynx was open-sourced in March 2025. The internal framework that powered TikTok and Douyin live commerce and mini-apps was released to the public for the first time.

Cross-platform no longer has a single right answer. This article surveys the 2026 mobile cross-platform stack in one breath.


1. The essential problem cross-platform solves

iOS uses Swift/Objective-C with UIKit/SwiftUI, while Android uses Kotlin/Java with Views/Jetpack Compose. The two platforms differ in language, UI toolkit, toolchain, and distribution channels. The urge to "write business logic and screens once and run them on both" is persistent for that very reason.

Cross-platform attacks this problem with five distinct strategies.

  • JavaScript bridge (classic React Native): JS code remote-controls native views.
  • JSI direct calls (modern React Native, New Architecture): JS and native share memory and call synchronously.
  • Self-rendered (Flutter, Lynx): skip platform views and paint directly to a canvas.
  • WebView (Capacitor, Ionic, Cordova): render web UI inside a native container.
  • Shared business logic (Kotlin Multiplatform, KMP): share business code; keep UI fully native.

The choice depends on the domain. Games favor self-rendered, internal tools are happy with WebView, and messaging or e-commerce tends to converge on New Architecture RN or KMP.


2. React Native New Architecture — JSI, Fabric, TurboModules

The New Architecture stabilized in React Native 0.76 has three parts.

  • JSI (JavaScript Interface): a thin interface between V8/Hermes and C++. JS objects are exposed as C++ HostObjects so calls happen without serialization.
  • Fabric: the new renderer. JS builds a React tree, which is turned into a C++ Shadow Tree and committed straight to native views on the main thread. Synchronous layout becomes possible.
  • TurboModules: native modules rewritten on top of JSI. Modules initialize lazily, and calls can be synchronous or asynchronous.

In classic RN, every call went through "serialize, JSON, native deserialize, async callback." Under the New Architecture, JS holds C++ pointers directly and calls functions on them. Hot reload is still fast, and debugging keeps using React DevTools.

// TurboModule definition: spec/NativeMathModule.ts
import type { TurboModule } from 'react-native'
import { TurboModuleRegistry } from 'react-native'

export interface Spec extends TurboModule {
  add(a: number, b: number): number
  factorial(n: number): Promise<number>
  getDeviceLocale(): string
}

export default TurboModuleRegistry.getEnforcing<Spec>('NativeMathModule')
// Usage: App.tsx
import NativeMathModule from './spec/NativeMathModule'

export default function App() {
  // Synchronous call — direct native invocation without a bridge
  const sum = NativeMathModule.add(2, 3)
  const locale = NativeMathModule.getDeviceLocale()
  return null
}

The codegen step reads the Spec interface and generates an Objective-C++ header on iOS and a Java/Kotlin interface on Android. Type safety is dramatically better than the legacy NativeModules.


3. Hermes — from JavaScriptCore to a custom engine

Hermes is the JS engine Meta built for mobile RN. In 2026 it is the default engine.

  • AOT bytecode compilation gives short start times.
  • The garbage collector is tuned for mobile (short stop-the-world).
  • Memory footprint is smaller than V8.
  • Most ES2015+ is supported, with some Proxy and regex limits.

Historically iOS RN used JavaScriptCore (JSC) and Android used V8 or JSC. Hermes guarantees identical behavior across both platforms while cutting start time nearly in half. That is why <200ms cold start becomes possible.


4. Flutter — from Skia to Impeller

Flutter has been a self-rendered framework from day one. It does not use UIKit or platform Views; instead it draws pixels directly via Skia. The change in 2026 is that the rendering engine moved from Skia to Impeller.

  • Skia: Chromium's 2D graphics engine, used since the early days of Flutter. Strong on compatibility and maturity. Weak on first-frame jank caused by runtime shader compilation.
  • Impeller: a renderer built by the Flutter team. It precompiles shaders at build time and talks directly to Metal (iOS) and Vulkan (Android). First-frame jank is essentially gone.

From Flutter 3.27 onward, Impeller is the default on both iOS and Android. Some complex shader effects still fall back to Skia, but 99% of typical apps run smoothly on Impeller.

// Flutter: default Material app
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '2026 Cross-Platform Demo',
      theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});
  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _count = 0;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Center(child: Text('Count: $_count', style: const TextStyle(fontSize: 32))),
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState(() => _count++),
        child: const Icon(Icons.add),
      ),
    );
  }
}

Dart is AOT-compiled to native machine code, so there is no JIT warm-up. Startup is fast and holding 60fps is comparatively easy. The downside is binary size — a baseline Flutter app takes around 15MB on iOS and 8-10MB on Android.


5. Expo — the de facto standard for RN developer experience

Expo started as "an easy way to begin with RN" and by 2026 it has become the de facto standard development environment for React Native. Core components of Expo SDK 53 (first half of 2026):

  • EAS Build: cloud build service. iOS would normally need a Mac, but EAS handles it in the cloud.
  • EAS Submit: automatic submissions to the App Store and Play Store.
  • EAS Update: over-the-air updates. JS-only updates that ship without changing native code.
  • Expo Router: file-based routing. Brings a Next.js-style app/ directory layout to RN.
  • Expo Atlas: bundle analyzer that visualizes which modules consume bundle size.
  • Expo Modules: a wrapper that makes writing TurboModules easier. Author modules in Swift/Kotlin and get automatic JS bindings.
# Create a new app with Expo SDK 53
npx create-expo-app@latest my-app --template default

cd my-app

# Local development
npx expo start

# Cloud builds
npx eas build --platform ios --profile development
npx eas build --platform android --profile production

# Push an OTA update
npx eas update --branch production --message "fix: button text typo"

Expo Router fundamentally changed RN routing.

# File layout
app/
  _layout.tsx       # root layout (tabs or stack)
  index.tsx         # home (/)
  login.tsx         # login (/login)
  (tabs)/
    _layout.tsx     # tab layout
    index.tsx       # tab home
    profile.tsx     # tab profile (/profile)
  product/
    [id].tsx        # dynamic route (/product/123)

Compared with the old RN pattern of hand-wiring React Navigation, Expo Router is essentially zero learning cost for a developer coming from Next.js.


6. Capacitor 7 plus Ionic — the WebView camp evolves

Capacitor is a WebView-based cross-platform runtime from the Ionic team. It is the successor to Cordova, and Capacitor 7 in 2025 brought meaningful evolution.

  • Render a web UI (React, Vue, Angular, Svelte — your choice) inside a native container.
  • Access native features (camera, GPS, push, files) through a plugin system.
  • Produces 100% native build outputs (.ipa, .apk).
  • Lets you port a PWA into a mobile app almost as-is.
// Capacitor: using the camera plugin
import { Camera, CameraResultType } from '@capacitor/camera'

async function takePhoto() {
  const photo = await Camera.getPhoto({
    quality: 90,
    allowEditing: false,
    resultType: CameraResultType.Uri,
  })
  // photo.webPath can be used directly as the src of an img element
  return photo.webPath
}

The key change in Capacitor 7: iOS WKWebView and Android WebView have both been unified to modern engines. As a result, modern CSS (Container Queries, View Transitions API) and parts of WebGPU work.

When does Capacitor fit? Content-heavy apps, internal tools, MVPs where time-to-market is the priority, and reusing existing web team code. When does it not fit? Heavy animation, games, and feeds where smooth 60fps scrolling is mandatory.


7. Tauri 2 Mobile — Rust on mobile

Tauri 2.0 in October 2024 was one of the biggest events. Tauri had been desktop-only (Rust backend plus WebView frontend), but starting with 2.0 it officially supports iOS and Android.

  • The backend is written in Rust. It builds as a static library on iOS and via JNI on Android.
  • The frontend uses the system WebView (WKWebView on iOS, Android WebView). Pick any web framework (React/Vue/Svelte).
  • IPC (Inter-Process Communication) is standardized through commands and events.
  • Binaries are smaller than React Native or Flutter (<10MB is achievable).
# Initialize a Tauri 2 mobile project
npm create tauri-app@latest my-mobile-app
cd my-mobile-app

# Add the Android target
npm run tauri android init
npm run tauri android dev

# Add the iOS target
npm run tauri ios init
npm run tauri ios dev
# tauri.conf.json (summary)
app:
  product-name: my-mobile-app
  identifier: com.example.app
  windows:
    - title: my-mobile-app
      width: 800
      height: 600
bundle:
  active: true
  targets: all
  android:
    minSdkVersion: 24
  iOS:
    minimumSystemVersion: '14.0'

Tauri 2 Mobile is not yet as mature as RN or Flutter. Hot reload exists, but the plugin ecosystem is much smaller and App Store review experience is thin. Still, "Rust backend plus web frontend plus tiny binary" is an attractive combination for systems programmers.


8. Kotlin Multiplatform — the canonical approach to sharing business logic

JetBrains' Kotlin Multiplatform (KMP, formerly KMM) takes a different road. UI is native per platform (SwiftUI on iOS, Compose on Android), and only business logic and the data layer are shared.

  • commonMain: shared code (Kotlin).
  • androidMain: Android-only.
  • iosMain: iOS-only. Compiled with Kotlin/Native into an iOS framework (.framework) and imported from Swift.
  • The expect/actual keywords let you branch platform-specific implementations.
// shared/src/commonMain/kotlin/Repository.kt
class UserRepository(private val api: UserApi) {
    suspend fun fetchUser(id: String): User = api.getUser(id)
}

expect class PlatformLogger() {
    fun log(message: String)
}
// shared/src/androidMain/kotlin/PlatformLogger.android.kt
actual class PlatformLogger actual constructor() {
    actual fun log(message: String) {
        android.util.Log.d("KMP", message)
    }
}
// iosApp/iosApp/ContentView.swift
import shared

struct ContentView: View {
    let repo = UserRepository(api: UserApi())

    var body: some View {
        Text("Hello, KMP!")
            .task {
                let user = try? await repo.fetchUser(id: "1")
            }
    }
}

The big strength of KMP: you use native UI as-is. SwiftUI on iOS, Compose on Android, perfectly following both design guidelines. The downside is having to write UI twice.

Toss, Daangn (Karrot), LINE, and Mercari have partially adopted KMP, and in 2025-2026 Kakao and Rakuten announced they were evaluating it.


9. Compose Multiplatform — KMP extended to shared UI

JetBrains created Compose Multiplatform to address KMP's limitation (UI written twice). It brings Android's Jetpack Compose to iOS, desktop, and the web.

  • The same @Composable function runs on Android, iOS, desktop, and the web.
  • iOS runs Kotlin/Native plus a Skia-based renderer (similar to Flutter).
  • Android keeps using Jetpack Compose directly, fully compatible with Material 3.

From Compose Multiplatform 1.7 (released 2025), iOS reached stable. JetBrains demos and PoC results from Kakao and Toss show 60fps is generally achievable.

// shared/src/commonMain/kotlin/App.kt
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun App() {
    var count by remember { mutableStateOf(0) }

    MaterialTheme {
        Column(modifier = Modifier.padding(16.dp)) {
            Text("Compose Multiplatform on iOS/Android")
            Button(onClick = { count++ }) {
                Text("Clicked $count times")
            }
        }
    }
}

Pros: truly write once. Cons: extra work needed to match the iOS native look and feel precisely. Similar to Flutter, but with the differentiator of Kotlin.


10. .NET MAUI — Xamarin's successor

Microsoft's .NET MAUI (Multi-platform App UI) is the successor to Xamarin.Forms. Built on .NET 9, it targets iOS, Android, macOS, and Windows.

  • Single project for multiple platforms.
  • UI written in C# plus XAML.
  • Legacy Xamarin Native (Xamarin.iOS, Xamarin.Android) reached EOL in May 2024.
  • Supports Hot Reload and a .NET MAUI Blazor hybrid (WebView plus .NET).
# Create a new .NET MAUI project
dotnet new maui -n MyApp
cd MyApp

# iOS build (on macOS)
dotnet build -t:Run -f net9.0-ios

# Android build
dotnet build -t:Run -f net9.0-android

.NET MAUI is the natural choice when an enterprise .NET team moves to mobile. It has share in the Japanese SI market and is adopted in parts of the Korean financial sector. The downside is that the mobile community is smaller than RN or Flutter.


11. NativeScript — together with Vue and Angular

NativeScript sits between Cordova/Capacitor and React Native. It does not use a WebView, but JS manipulates native views directly. While RN was heading toward JSI, NativeScript followed a similar path with its own V8 integration.

  • Integrates well with Vue, Angular, and Svelte (especially popular with Vue/Angular teams).
  • Exposes Objective-C++ on iOS and Java on Android as metadata, then calls them directly from JS.
  • The old NativeScript Core is in maintenance mode; @nativescript/core 8.x is current.
  • The community is smaller than RN or Flutter, but for mobile work backed by Vue, it remains a valid choice.

NativeScript holds a niche position in 2026, but it has the lowest learning cost when bringing a Vue or Angular codebase to mobile.


12. ByteDance Lynx — the new face of 2025

In March 2025, ByteDance open-sourced its internal framework Lynx. It powers TikTok and Douyin live commerce UIs, mini-app containers, comment screens, and more.

Lynx's design philosophy:

  • Render engine separation: split the JS thread from the rendering thread. Even when JS stalls, the UI keeps 60fps.
  • CSS-in-Style: a syntax closer to real CSS than React Native's StyleSheet. Full Flexbox, Grid, and animation support.
  • Multi-threading: designed from day one so JS does not block the main thread.
  • Web compatibility: abstractions allow the same code to run on React DOM and on Lynx.
// Lynx component (TypeScript plus JSX)
import { Component } from '@lynx-js/react'

class Counter extends Component {
  state = { count: 0 }

  render() {
    return (
      <view className="container">
        <text className="title">Lynx Counter</text>
        <text className="count">Count: {this.state.count}</text>
        <view bindtap={() => this.setState({ count: this.state.count + 1 })}>
          <text>Tap to increment</text>
        </view>
      </view>
    )
  }
}

Lynx is not as mature as RN or Flutter in 2026, but having a giant user (ByteDance) plus open-source status is appealing. Adoption is rare in Korea and Japan today, but live-commerce-heavy companies are watching.


13. Rendering model comparison

FrameworkRendering approachGraphics APIFirst-frame jank60fps difficulty
React Native (New Arch)Native views plus FabricUIKit / Android ViewsLowMedium
Flutter (Impeller)Self-renderedMetal / VulkanVery lowEasy
CapacitorWebViewWebKit / BlinkMediumMedium
Tauri 2 MobileWebViewWebKit / BlinkMediumMedium
Compose MultiplatformSkiaMetal / OpenGLLowEasy
.NET MAUINative viewsUIKit / Android ViewsMediumMedium
NativeScriptNative viewsUIKit / Android ViewsLowMedium
LynxSelf-rendered plus thread splitMetal / OpenGLLowEasy
KMP (UI separate)100% nativeUIKit / ComposeLowestEasiest

Self-rendered camps (Flutter, Lynx, Compose MP) make holding 60fps easiest. WebView (Capacitor, Tauri) suits content-driven apps. Native-view camps (RN, MAUI, NativeScript) follow OS UI guidelines best.


14. Bundle and binary size comparison

For the same "Hello World plus counter plus network request" app (approximate sizes in 2026, iOS Release builds):

FrameworkiOS .ipaAndroid .apkNotes
React Native (Hermes)~10MB~7MBIncludes Hermes bytecode
Flutter (Impeller, AOT)~15MB~9MBDart runtime plus icon fonts
Capacitor~5MB~3MBWebView itself ships with the OS
Tauri 2 Mobile~4MB~3MBRust static linking
KMP (native UI)~3MB~2MBNative by default
Compose Multiplatform~12MB~5MBiOS bundles Skia
.NET MAUI~25MB~15MBLarge .NET runtime
NativeScript~10MB~8MBIncludes a JS engine
Lynx~8MB~6MBSelf-rendered

KMP (native UI) and Capacitor/Tauri (WebView) are smallest. .NET MAUI is largest.


15. Native module bridges — how each framework reaches native

How each framework accesses native features.

FrameworkMechanismImplementation languagesType safety
React Native (TurboModules)JSI HostObjectObj-C++ / Java / Kotlin / SwiftStrong via codegen
FlutterPlatform Channels (MethodChannel)Swift / KotlinSerialization-based, manual
CapacitorCapacitor PluginSwift / JavaDecorator-based, medium
Tauri 2Command plus IPCRustStrong via Serde
KMP / Compose MPexpect/actualKotlin / Swift interopKotlin types as-is
.NET MAUIDependencyService / HandlerC#C# types as-is
NativeScriptMetadata plus reflectionJS plus Obj-C/Java metadataRuntime
LynxNative ModuleSwift / KotlinTypeScript codegen
// React Native TurboModule (iOS) — Swift side
@objc(NativeMathModule)
class NativeMathModule: NSObject {
  @objc func add(_ a: Double, b: Double) -> NSNumber {
    return NSNumber(value: a + b)
  }
}
// Flutter MethodChannel (Android, Kotlin)
import io.flutter.plugin.common.MethodChannel

class MainActivity : FlutterActivity() {
    private val CHANNEL = "com.example.math"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
            .setMethodCallHandler { call, result ->
                if (call.method == "add") {
                    val a = call.argument<Int>("a") ?: 0
                    val b = call.argument<Int>("b") ?: 0
                    result.success(a + b)
                } else {
                    result.notImplemented()
                }
            }
    }
}

TurboModules and Tauri Commands are the strongest on type safety. NativeScript is the weakest because it leans on runtime metadata.


16. Hot reload and developer experience

FrameworkHot ReloadHot RestartDevToolsDebugger
React NativeFast RefreshYesReact DevToolsChrome / Flipper / Reactotron
FlutterVery fastYesFlutter DevToolsDart DevTools
CapacitorSame as webYesBrowser DevToolsChrome / Safari
Tauri 2 MobileFastYesWeb plus Rust bothWeb DevTools plus lldb
KMPStandard IDEYesAndroid Studio / XcodeBoth debuggers
Compose MultiplatformFastYesCompose PreviewIntelliJ / Xcode
.NET MAUIXAML Hot ReloadYesVisual StudioVS / Rider
LynxFastYesLynx DevToolsChrome-based

Flutter's Hot Reload remains the industry leader. State preservation plus widget tree rebuild typically finishes in <500ms. RN Fast Refresh also got a step faster under the New Architecture.


17. Performance benchmarks — the cost of holding 60fps

General benchmarks in 2026 (on a mid-tier Android device, roughly a Pixel 6a).

FrameworkCold startScroll 60fpsMemory (dashboard)Battery (1h use)
React Native (New Arch plus Hermes)~250msAchievable~120MB~7%
Flutter (Impeller, AOT)~150msVery easy~100MB~6%
Capacitor~400msHard~150MB~9%
Tauri 2 Mobile~300msHard~110MB~8%
KMP plus native UI~120msEasiest~80MB~5%
Compose Multiplatform~200msEasy~110MB~6%
.NET MAUI~500msMedium~180MB~10%
Lynx~200msVery easy (thread split)~95MB~6%

KMP wins decisively for near-native performance, with self-rendered camps (Flutter, Lynx, Compose MP) close behind. WebView camps are weaker for heavy interaction.


18. Korea — Toss migrating RN to native, and KakaoTalk adopting RN

Toss used RN aggressively in 2020-2022, building several screens (transfers, cards, and more) in RN. According to Toss talks in 2023-2024, some core screens were migrated from RN back to native. The reasons: difficulty holding 60fps, security requirements of payment screens, and widgets and deep-link integrations that only native can provide. Parts of Toss Securities and several internal tools still use RN.

KakaoTalk keeps the messaging screen native, but parts of Kakao Channel, Kakao Shopping, and KakaoPay are built in RN or in WebView. Kakao Enterprise announced a PoC for adopting KMP in 2024.

Daangn Market (Karrot) adopted RN early and reported in 2024 that scroll performance improved roughly 30% after migrating to the RN New Architecture.

NAVER LINE keeps the messenger native in its Japan HQ, but parts of LINE Manga and LINE Music are written in Flutter.


19. Japan — Mercari, Rakuten, ZOZO, and Mercari Flutter

Mercari has been adopting Flutter for several screens since its 2018 PoC announcement, and from 2022 onward it migrated the new listing flow to Flutter. By a 2024 talk, about 70% of the listing flow is on Flutter.

Rakuten runs a massive super-app ecosystem and has adopted Flutter in some apps (Rakuten Kiosk, parts of Rakuten Pay). The main Rakuten app remains native.

ZOZO (ZOZOTOWN) built the ZOZOFIT (fitting suit) app in Flutter in 2023. It deals with camera input and 3D modeling, with UI in Flutter and the core in native modules.

LINE Manga (Japan) has several screens written in Flutter while the content viewer itself stays native. CyberAgent has also adopted Flutter in several subsidiary apps.

Japan has a higher Flutter adoption rate than Korea, and the Flutter Tokyo community is very active thanks in part to Mercari.


20. OTA updates and App Store policy

The iOS App Store and Google Play prohibit "updating native code over-the-air." Interpreted content like JS bundles, however, is allowed. That is why RN, Capacitor, Tauri, and Lynx can do OTA updates.

  • Expo EAS Update: the most mature for RN. Canary rollout via branches and channels.
  • CodePush (Microsoft, EOL 2024): shut down, migrate to EAS.
  • Capacitor Live Updates (Ionic): a paid service offering OTA.
  • Roll your own: build with S3 plus a version manifest.

The OTA pitfall is App Store terms. Changing core features, payment flows, or adding new categories needs a regular review. To stay safe, OTA should be limited to bug fixes, copy changes, and small UI tweaks.


21. Decision matrix — when to use which tool

SituationRecommendation
Native look and feel, must follow both OS guidelinesKMP plus native UI
Write once, 60fps, rich animationFlutter
Reuse web team code, fast time-to-marketCapacitor or Tauri 2
React team moving to mobileReact Native plus Expo
Existing .NET backend team moving to mobile.NET MAUI
Vue or Angular team moving to mobileNativeScript or Capacitor
Kotlin team needs iOS support tooCompose Multiplatform
Live commerce or mini-app patterns like ByteDanceLynx
Reusing a Rust backend on mobileTauri 2 Mobile
GamesUnity, Unreal, Godot (out of scope)

There is no single right answer. Existing tech stack, design guideline adherence, time-to-market, and 60fps requirements all factor in.


22. Monorepo strategy — Turborepo, Nx, Yarn Workspaces

Cross-platform apps usually share a repository with the web. Common 2026 patterns:

  • Turborepo plus pnpm: React Native, Next.js web, and shared packages.
  • Nx: popular for teams running RN, Angular, and NativeScript together.
  • Yarn Workspaces: simple pattern when build matrices are small.
  • Melos (for Flutter): monorepo tooling dedicated to Flutter packages.
# A Turborepo pattern
my-org/
  apps/
    mobile/          # Expo RN app
    web/             # Next.js web
  packages/
    ui/              # Design system (RN plus web compatible)
    api-client/      # Shared API client
    config/          # ESLint and TS config
  turbo.json
  package.json

RN can share some code with React on the web via react-native-web. Monorepos really shine at the design-system layer.


23. CI/CD — Fastlane, Codemagic, EAS, App Center

iOS builds need macOS, and code signing is famously fiddly. Major options in 2026:

  • Fastlane: the oldest and most mature. Self-hosted or integrated with GitHub Actions.
  • Codemagic: Flutter-friendly SaaS that provides macOS build machines.
  • EAS Build (Expo): RN and Expo-focused SaaS that manages iOS certificates automatically.
  • Bitrise: strong on both iOS and Android, popular in enterprises.
  • GitHub Actions plus a macOS runner: roll your own. Most flexible, but you manage certificates yourself.
  • App Center (Microsoft, EOL 2025): shutting down; migrate to other services.

Code signing pitfalls: an Apple Developer account ($99/yr), provisioning profiles, push notification certificates, weak automatic renewal — expiry leads to broken builds. Storing them encrypted in Git via Match (Fastlane) is the de facto standard.


24. Design systems, accessibility, internationalization

FrameworkMaterial DesignCupertino (iOS look)a11yi18n
React NativeRN Paper, TamaguiiOS-default componentsaccessibilityRolei18next, FormatJS
FlutterMaterial widgets (default)Cupertino widgets (separate)Semanticsintl package
Compose MPMaterial 3 (default)Cupertino-like librariesCompose Semanticsmoko-resources
CapacitorIonic ComponentsIonic iOS themingWeb-standard ARIAi18next
Tauri 2Web freedomWeb freedomWeb-standard ARIAi18next
KMP (native)Android as-isiOS SwiftUI as-isNative toolingAndroid resources / iOS Localizable.strings
.NET MAUIPartial MaterialPartial iOSAutomationProperties.resx files
NativeScriptWeb-compatible componentsiOS-like componentsARIA-likei18next

Accessibility is strongest for KMP, .NET MAUI, and NativeScript (they leverage native tools directly). Capacitor and Tauri ride on web a11y standards. Flutter needs explicit Semantics widgets.


25. Security, authentication, and payments

Mobile apps have stricter security requirements than the web.

  • Keychain (iOS) and Keystore (Android): secure storage for tokens and passwords.
  • Biometrics: Face ID, Touch ID, BiometricPrompt. RN uses react-native-keychain plus react-native-biometrics; Flutter uses local_auth.
  • In-app purchases: Apple IAP, Google Billing. RN uses react-native-iap; Flutter uses in_app_purchase. KMP calls native code directly.
  • Push notifications: FCM (Android), APNs (iOS). Expo Notifications, Flutter Firebase Messaging.
  • App integrity: iOS DeviceCheck, Android Play Integrity. Detect rooting and jailbreaks.

Teams that built payment screens in RN often regret it because Apple and Google are strict about non-IAP payments, and security reviews are harder for RN screens than for native. That is one reason Toss moved some screens back to native.


26. Epilogue — the 2027 outlook

Cross-platform is no longer a single "write once, run both" promise. Three changes likely in 2027:

  1. AI code generation cuts the cost of writing both platform codebases. When Cursor and Claude Code routinely generate SwiftUI and Compose at the same time, some of the value of "shared code" gets eroded. Still, shared business logic (KMP) remains attractive.
  2. The spread of self-rendered camps. Flutter Impeller becomes the standard, Lynx grows share in live commerce, and Compose Multiplatform settles into stable on iOS.
  3. A renaissance of the WebView camps. Capacitor 7 and Tauri 2 Mobile grow share alongside the evolution of system WebViews (WebKit 17, Android WebView 130 and beyond). Porting a PWA to a mobile app is cheapest of all.

The biggest lesson is simple. Not every screen needs to be written with the same tool. Just as Toss runs payments in native while keeping internal tooling in RN, picking the right tool screen by screen within a single company is the 2026 answer.


References