react / react/metro

React Native DevTools not working on iOS physical device - Shows "Connect to Metro to debug JavaScript" Description

Open
#1,626 1 comment 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
5.6k
Forks
696
Avg merge
8m
Merged PRs (30d)
7

Description

React Native DevTools not working on iOS physical device - Shows "Connect to Metro to debug JavaScript"

Description

I'm unable to open the React Native DevTools on iOS physical devices in React Native 0.82.1. When I start Metro and press j in the terminal, the DevTools don't open. Similarly, when I open the Dev Menu it only shows the message "Connect to Metro to debug JavaScript" insted of open devtool. The app connects to Metro bundler successfully and hot reload works fine, but the debugger won't open at all.

Important notes:

  • ✅ Works fine on Android physical devices
  • ✅ Metro bundler is running and app connects successfully (reload works)
  • ❌ DevTools not working on iOS physical device
  • 🔄 Recently migrated AppDelegate from Objective-C (.mm) to Swift

Environment

React Native Info
React Native: 0.82.1
Node: v24.2.0
iOS: 15.5+
Platform: iOS (Physical Device)
Hermes: Enabled
New Architecture: Enabled
Device
  • Physical iPhone device (not simulator)
  • Connected via USB and same WiFi network as development machine

Steps to Reproduce

  1. Create/use React Native 0.82.1 project with Hermes and New Architecture enabled
  2. Migrate AppDelegate from Objective-C to Swift (files shown below)
  3. Build and run on physical iOS device
  4. Shake device to open Dev Menu
  5. Tap "Open Debugger"
  6. Expected: Chrome DevTools opens with Hermes debugger
  7. Actual: Shows message "Connect to Metro to debug JavaScript" but debugger doesn't open

Relevant Code

AppDelegate.swift (Converted from .mm)
import React
import React_RCTAppDelegate
import ReactAppDependencyProvider
import Firebase
import RNBootSplash
import FBSDKCoreKit
import AppTrackingTransparency
import GoogleSignIn

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
  
  var window: UIWindow?
  var reactNativeDelegate: ReactNativeDelegate?
  var reactNativeFactory: RCTReactNativeFactory?
  
  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {
    
    // Initialize Firebase
    if FirebaseApp.app() == nil {
      var googleServiceInfofilePath = ""
      #if DEV
        googleServiceInfofilePath = Bundle.main.path(forResource: "GoogleServices-Info-Dev", ofType: "plist") ?? ""
      #else
        googleServiceInfofilePath = Bundle.main.path(forResource: "GoogleServices-Info", ofType: "plist") ?? ""
      #endif
      FirebaseApp.configure()
    }
    
    // Initialize Facebook SDK
    ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)
    
    // Request App Tracking Transparency (iOS 14+)
    if #available(iOS 14, *) {
      ATTrackingManager.requestTrackingAuthorization { _ in
        AppEvents.shared.activateApp()
      }
    } else {
      AppEvents.shared.activateApp()
    }
    
    // Setup React Native
    let delegate = ReactNativeDelegate()
    let factory = RCTReactNativeFactory(delegate: delegate)
    delegate.dependencyProvider = RCTAppDependencyProvider()
    reactNativeDelegate = delegate
    reactNativeFactory = factory
    
    window = UIWindow(frame: UIScreen.main.bounds)
    
    factory.startReactNative(
      withModuleName: "Fin1",
      in: window,
      launchOptions: launchOptions
    )
    
    return true
  }
  
  func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
    return Orientation.getOrientation()
  }
  
  func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    // Add any other URL handlers you're using (e.g. Facebook SDK)
    return ApplicationDelegate.shared.application(app, open: url, options: options) ||
           GIDSignIn.sharedInstance.handle(url) ||
           RCTLinkingManager.application(app, open: url, options: options)
  }
  
  func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    return RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
  }
}

class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
  
  override func sourceURL(for bridge: RCTBridge) -> URL? {
    self.bundleURL()
  }

  override func customize(_ rootView: RCTRootView) {
    super.customize(rootView)
    RNBootSplash.initWithStoryboard("BootSplash", rootView: rootView)
  }

  override func bundleURL() -> URL? {
    #if DEBUG
      return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
    #else
      return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
    #endif
    } 
}

Previous AppDelegate.mm (Original - Before Swift Conversion)
#import "AppDelegate.h"
 
 #import <React/RCTBundleURLProvider.h>
 #import <Firebase.h>
 #import "RNSplashScreen.h"
 #import <FBSDKCoreKit/FBSDKCoreKit-swift.h>
 #import "Orientation.h"
 #import <React/RCTLinkingManager.h>
 #import <ReactAppDependencyProvider/RCTAppDependencyProvider.h> 

 
 @implementation AppDelegate
 
 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
 {
   self.moduleName = @"Fin1";
   self.dependencyProvider = [RCTAppDependencyProvider new];
   if ([FIRApp defaultApp] == nil) {
     NSString *googleServiceInfofilePath = @"";
     #if DEV
       googleServiceInfofilePath = [[NSBundle mainBundle] pathForResource:@"GoogleServices-Info-Dev" ofType:@"plist"];
     #else
       googleServiceInfofilePath = [[NSBundle mainBundle] pathForResource:@"GoogleServices-Info" ofType:@"plist"];
     #endif
     [FIRApp configure];
   }
 
   [FBSDKApplicationDelegate.sharedInstance initializeSDK];
   [[FBSDKApplicationDelegate sharedInstance] application:application
                        didFinishLaunchingWithOptions:launchOptions];
   
   // You can add your custom initial props in the dictionary below.
   // They will be passed down to the ViewController used by React Native.
   self.initialProps = @{};
   return [super application:application didFinishLaunchingWithOptions:launchOptions];
 }
 
 - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
 {
   return [self bundleURL];
 }
  
 - (NSURL *)bundleURL
 
 {
 #if DEBUG
   return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
 #else
   return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
 #endif
 }
 
 - (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
   return [Orientation getOrientation];
 }
 
 - (BOOL)application:(UIApplication *)application
    openURL:(NSURL *)url
    options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
 {
   return [RCTLinkingManager application:application openURL:url options:options];
 }
 
 - (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
  restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
 {
  return [RCTLinkingManager application:application
                   continueUserActivity:userActivity
                     restorationHandler:restorationHandler];
 }
 // - (BOOL)application:(UIApplication *)application 
 //             openURL:(NSURL *)url 
 //             options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
 //   // Add any custom logic here.
 //   return [[FBSDKApplicationDelegate sharedInstance]application:application
 //                                                       openURL:url
 //                                                       options:options];
 // }
 
	 @end
metro.config.js
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
const {
  wrapWithReanimatedMetroConfig,
} = require('react-native-reanimated/metro-config');

/**
 * Metro configuration
 * https://reactnative.dev/docs/metro
 *
 * @type {import('@react-native/metro-config').MetroConfig}
 */
const config = {};

const mergedConfig = mergeConfig(getDefaultConfig(__dirname), config);

module.exports = wrapWithReanimatedMetroConfig(mergedConfig);

Podfile
require Pod::Executable.execute_command('node', ['-p',
  'require.resolve(
    "react-native/scripts/react_native_pods.rb",
    {paths: [process.argv[1]]},
  )', __dir__]).strip

platform :ios, 15.5
prepare_react_native_project!

linkage = ENV['USE_FRAMEWORKS']
if linkage != nil
  Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
  use_frameworks! :linkage => linkage.to_sym
end

project 'Fin1',
        'Dev.Debug' => :debug,
        'Dev.Release' => :release

target 'Fin1' do
  config = use_native_modules!
 
  use_frameworks! :linkage => :static
  $RNFirebaseAsStaticFramework = true
  use_react_native!(
    :path => config[:reactNativePath],

    # An absolute path to your application root.
    :app_path => "#{Pod::Config.instance.installation_root}/..",
    :fabric_enabled => true,
    :hermes_enabled => true,
    :new_arch_enabled => true 
  )
  
  pod 'XCDYouTubeKit'
  pod 'FBSDKCoreKit'

  post_install do |installer|
    react_native_post_install(
      installer,
      config[:reactNativePath],
       :mac_catalyst_enabled => false,
      # :ccache_enabled => true
    )
    
    bitcode_strip_path = `xcrun --find bitcode_strip`.chop!

    def strip_bitcode_from_framework(bitcode_strip_path, framework_relative_path)
      framework_path = File.join(Dir.pwd, framework_relative_path)
      command = "#{bitcode_strip_path} #{framework_path} -r -o #{framework_path}"
      puts "Stripping bitcode: #{command}"
      system(command)
    end

    framework_paths = [
      "Pods/VdoFramework/VdoFramework.xcframework/ios-arm64/VdoFramework.framework/VdoFramework"
    ]

    framework_paths.each do |framework_relative_path|
      strip_bitcode_from_framework(bitcode_strip_path, framework_relative_path)
  end
end
end

What I've Tried

  1. ✅ Verified Hermes is enabled in Podfile
  2. ✅ Cleaned build folder and reinstalled pods multiple times
  3. ✅ Verified Metro bundler is running and accessible
  4. ✅ Checked chrome://inspect - no "Hermes React Native" target appears
  5. ✅ Tried pressing j in Metro terminal - no debugger opens
  6. ✅ Verified app is built in Debug mode (Dev.Debug configuration)
  7. ✅ Both Mac and iPhone are on same WiFi network
  8. ✅ Manually configured bundler IP address via "Configure Bundler" in Dev Menu
  9. ❌ Still can't open debugger

Additional Context

  • This issue started after upgrading from an earlier React Native version where debugging worked fine
  • Android physical device debugging works perfectly with the same setup
  • The Swift conversion was done carefully to match the Objective-C implementation

Questions

  1. Is there a known issue with React Native DevTools on iOS physical devices in 0.82.1?
  2. Could the Swift migration be causing debugger initialization issues?
  3. Are there additional configurations needed for physical device debugging in 0.82+?

Any help would be greatly appreciated! This is blocking our development workflow on iOS devices.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reproducing the debugger failure on an iOS physical device with React Native 0.82.1, then compare AppDelegate.swift with the previous AppDelegate.mm. Review metro.config.js and the Podfile alongside the Dev Menu and Metro behavior. Done means identifying a reproducible cause and documenting or validating a fix for opening React Native DevTools on iOS devices.

Written by the indexing model from the issue text.

Assessment

Tech stack
ios, javascript, react-native, swift
Domain
devtools, mobile-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.