Handling Navigation in React Native Brownfield Apps

Handling navigation and presenting existing native screens from React Native-controlled screens in brownfield applications has traditionally been a tricky part of integration. There are multiple ways to make it work, but the implementation is often non-trivial, especially when you are just getting started with brownfield architecture.

This is exactly why we built: @callstack/brownfield-navigation. It helps React Native and native teams wire navigation in a clean, typed, and maintainable way, while improving developer experience on both sides.

This article is divided into three parts:

  • React Native side
  • Native App side
  • Workflow

React Native Side

Start by installing the package in your React Native app:

yarn add @callstack/brownfield-navigation

Ensure your app has Babel dependencies available (@babel/core, @react-native/babel-preset), which are used during codegen. If not, please install them.

Now define your navigation contract in a new file named brownfield.navigation.ts:

export interface BrownfieldNavigationSpec {
  navigateToSettings(): void;
  navigateToReferrals(userId: string): void;
}

Next, run codegen to generate Swift and Kotlin delegates from the schema:

npx brownfield navigation:codegen

You can skip this command in favor of running npx brownfield package:ios or npx brownfield package:android. These commands internally invoke codegen automatically. If you are not familiar with these commands, here is a quick read.

Finally, invoke the generated methods from React Native to present native screens:

import { Button, View } from "react-native";
import BrownfieldNavigation from "@callstack/brownfield-navigation";

export function NativeLinks() {
  return (
    <View>
      <Button
        title="Open native settings"
        onPress={() =>
          BrownfieldNavigation.navigateToSettings()
        }
      />
      <Button
        title="Open native referrals"
        onPress={() =>
          BrownfieldNavigation.navigateToReferrals(
            "user-123",
          )
        }
      />
    </View>
  );
}

That is all you need to handle on the React Native side.

React Native Brownfield is 100% compatible with Expo. If you want to try it on an Expo App, ensure you have followed the expo-integration docs first.

Native App Side

To complete the wiring, implement the generated delegate in your iOS application. But first, ensure you have the BrownfieldNavigation.xcframework linked to the project.

Depending on how your setup looks like, this step might differ. Dragging and dropping things into Xcode sounds fun the first time, but it gets tedious pretty fast. This is one of the first things we automate in all the incremental migration projects we do at Callstack.

Then, start by implementing BrownfieldNavigationDelegate:

import BrownfieldNavigation
import SwiftUI
import UIKit

public final class RNNavigationDelegate: BrownfieldNavigationDelegate {
  public func navigateToSettings() {
    present(SettingsScreen())
  }

  public func navigateToReferrals(_ userId: String) {
    present(ReferralsScreen(userId: userId))
  }

  private func present<Content: View>(_ view: Content) {
    DispatchQueue.main.async {
      let hostingController = UIHostingController(rootView: view)
      UIApplication.shared.topMostViewController()?
        .present(hostingController, animated: true)
    }
  }
}
The func present helper is private and not part of the delegate itself. Depending on your iOS app’s navigation structure, this implementation can vary.

Then register the delegate. An ideal place is before any React Native UI is presented:

import BrownfieldNavigation

@main
struct BrownfieldAppleApp: App {
  init() {
    BrownfieldNavigationManager.shared.setDelegate(
      navigationDelegate: RNNavigationDelegate()
    )
  }
}

That completes the native app setup. You can now run your iOS app and validate the flow.

Workflow

Codegen turns brownfield.navigation.ts into a stable, typed bridge surface that both teams implement against. The React Native team ships that surface inside artifacts (.xcframework / .aar) and hands those artifacts to native teams. Native apps consume the artifact and wire delegates to existing screens without depending on React Native internals or JavaScript tooling.

This contract-plus-artifact handoff reduces coupling and makes ownership boundaries and delivery steps simpler for both teams.

To summarize, this is how the workflow looks:

  1. You create a brownfield.navigation.ts spec in your React Native app.
  2. You run npx brownfield navigation:codegen to generate native bridge/delegate files.
  3. React Native calls BrownfieldNavigation.<method>().
  4. Native host code implements BrownfieldNavigationDelegate.
  5. You register the delegate at startup.

Each time you change brownfield.navigation.ts:

  1. You run npx brownfield navigation:codegen to regenerate native bridge/delegate files.
  2. Update your React Native call site, if required.
  3. Update native host code implementation, if required.
You can skip npx brownfield navigation:codegen in favor of npx brownfield package:ios / npx brownfield package:android.
The codegen command is intended for development only. When packaging your xcframework or aar, code generation is handled automatically by the package commands.

Final Words

@callstack/brownfield-navigation is designed to improve developer experience for both native and React Native teams, while reducing long-term maintenance costs for navigation handoff scenarios. It is especially useful in advanced entry points such as deep links and push notifications, where an imperative API can be called from anywhere in React Native.

To learn more about the complete setup and integration details, check out the Brownfield Navigation docs.

Insights

Learn more about Brownfield

cover

Aug 27Article

React Native in .NET MAUI: A Practical Brownfield Integration Guide

Learn how to embed React Native inside a .NET MAUI app with a brownfield setup. This guide covers runtime packaging, lifecycle wiring, navigation, and shared state across both runtimes.

cover

Aug 3Article

Migration to React Native in 2026 Starts With a Delivery Question

Choosing between brownfield and greenfield React Native migration? Learn where risk sits, when to use a checkpoint, and what to prove before committing.

cover

Jul 8Article

How we made Hybrid iOS apps compile 39% faster with React Native Brownfield

See how React Native Brownfield uses iOS prebuilts to cut packaging times by up to 39% for hybrid apps, with details on setup, internals, and benchmark results.

cover

Jun 18Article

How We Made the Brownfield Gradle Plugin 22% Faster

See how task-driven Gradle optimization made the Brownfield Gradle Plugin 22% faster for clean Android release builds.

cover

Jun 3Article

AI-Supported Brownfield Migration to React Native

See how AI-supported workflows cut brownfield React Native evaluation from weeks to days by speeding up setup, planning, implementation, and QA.

cover

May 18Article

Unlocking Expo Updates in an Isolated Brownfield Architecture with SDK 55

Learn how to enable Expo Updates in an Isolated Brownfield setup with Expo SDK 55, including iOS packaging, Android patching, and OTA testing.

cover

Apr 23Talk

State of Brownfield in React Native

A practical look at Brownfield React Native adoption in mature native apps, architecture, tooling, and real-world tradeoffs.

cover

Apr 2Webinar

AI-Assisted Migrations to React Native: From Months to Days

Reduce migration time from months to days by using AI in real React Native workflows. Learn when to migrate incrementally and when starting fresh is the better option.

cover

Nov 17Talk

Vega Developer Meetup

Learn how to integrate React Native into Android TV apps at the Vega Developer Meetup. Workshop, roadmap, and community talks, live in Munich.

cover

May 16Podcast

You Don’t Need a Rewrite, You Need React Native Brownfield

Discover how modern React Native Brownfield integration enables cross-platform power in native apps, without rewriting from scratch.

cover

Jan 13Podcast

Lessons from Shopify's Migration to React Native with Mustafa Ali

Learn how Shopify's migration to React Native led to 86% code unification, reduced build times, and seamless feature parity across platforms.

cover

Jun 14Podcast

Migration to React Native

The 13th episode of React Universe On Air (formerly known as The React Native Show) is fully focused on migrating from different technologies to React Native, the two most popular approaches to this process (greenfield and brownfield software development), and our state-of-the-art brownfield approach.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.