BS
BleepingSwift
Published on
6 min read

> Testing Expo and React Native on Device Over Local Network with Bonjour

When you're working on a development build of your Expo or React Native app, you need your physical device to connect to Metro running on your Mac. The standard approach involves finding your Mac's IP address and configuring it manually, but there's a cleaner way using Bonjour.

Bonjour is Apple's implementation of zero-configuration networking (mDNS/DNS-SD). It lets devices on the same local network discover each other by name instead of IP address. Your Mac already advertises itself as your-mac-name.local, and your iOS device can resolve that automatically.

Why Bonjour Over IP Addresses

IP addresses change. If you're on a laptop moving between home and office, or your DHCP lease renews, your IP might be different tomorrow. Hardcoding 192.168.1.x into your development setup means updating it constantly.

With Bonjour, you use your Mac's hostname instead. This stays consistent regardless of which network you're on or what IP you get assigned.

Finding Your Mac's Bonjour Hostname

Your Mac's local hostname is based on its computer name. Check it with:

scutil --get LocalHostName

This returns something like Micks-MacBook-Pro. Your full Bonjour address is that name plus .local, so Micks-MacBook-Pro.local.

You can also find this in System Settings > General > Sharing, where it shows your computer name and local hostname.

To verify it's working, ping it from another device on your network or from Terminal:

ping Micks-MacBook-Pro.local

Configuring Metro to Use Your Hostname

By default, Metro binds to localhost, which only accepts connections from the same machine. For device testing, you need Metro to listen on your network interface.

For Expo projects, set the REACT_NATIVE_PACKAGER_HOSTNAME environment variable:

REACT_NATIVE_PACKAGER_HOSTNAME=Micks-MacBook-Pro.local npx expo start

Or add it to your shell profile or a .env file. In your package.json scripts:

{
  "scripts": {
    "start": "REACT_NATIVE_PACKAGER_HOSTNAME=$(scutil --get LocalHostName).local expo start"
  }
}

This dynamically grabs your hostname, so it works across different machines without modification.

For bare React Native projects, you can set the same environment variable, or configure it in metro.config.js:

const { getDefaultConfig } = require('@react-native/metro-config')

const config = getDefaultConfig(__dirname)

config.server = {
  ...config.server,
  host: '0.0.0.0', // Listen on all interfaces
}

module.exports = config

Setting the host to 0.0.0.0 tells Metro to accept connections from any network interface, not just localhost.

Building a Development Client

This setup assumes you're using a development build rather than Expo Go. Development builds include your native code and connect to Metro for JavaScript updates.

Create a development build with:

npx expo run:ios --device

Or if you're using EAS Build:

eas build --profile development --platform ios

Once installed on your device, the development build needs to know where Metro is running. When you start Metro with the hostname configured, Expo automatically generates the correct connection URL.

Hardcoding the URL in Development Builds

If automatic discovery isn't working, you can set the bundler URL explicitly. Create or update your app.json or app.config.js:

export default {
  expo: {
    // ... other config
    extra: {
      // For development builds
      developmentClient: {
        silentLaunch: true,
      },
    },
    updates: {
      url: 'http://Micks-MacBook-Pro.local:8081',
    },
  },
}

For bare React Native, you can modify the dev menu settings or set the bundler location in AppDelegate.mm:

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"
    fallbackURLProvider:^NSURL *{
      return [NSURL URLWithString:@"http://Micks-MacBook-Pro.local:8081/index.bundle?platform=ios"];
    }];
}

macOS Firewall Configuration

The most common reason local network connections fail is the macOS firewall blocking incoming connections to Metro.

Check your firewall status in System Settings > Network > Firewall. If it's enabled, you have a few options.

The simplest is to allow incoming connections for Node.js when prompted. The first time a device tries to connect, macOS should show a dialog asking whether to allow node to accept connections. Click Allow.

If you missed that dialog or it never appeared, you can add Node manually. Go to System Settings > Network > Firewall > Options, then add /usr/local/bin/node (or wherever your Node binary lives) to the allowed applications list.

For Homebrew-installed Node via nvm, the path is usually something like:

which node
# /Users/you/.nvm/versions/node/v20.11.0/bin/node

Add that specific path to your firewall exceptions.

Alternatively, you can temporarily disable the firewall while developing, though this isn't recommended on public networks.

Troubleshooting Connection Issues

If your device can't connect to Metro, work through these checks.

First, verify both devices are on the same network. This sounds obvious, but if your Mac is on ethernet and your iPhone is on WiFi, they might be on different subnets. Some routers isolate wired and wireless clients.

Test Bonjour resolution from your device by opening Safari on your iPhone and navigating to http://Micks-MacBook-Pro.local:8081. You should see Metro's status page. If Safari can't resolve the hostname, Bonjour isn't working between your devices.

Some networks block mDNS traffic. Corporate networks and some guest networks disable multicast, which Bonjour requires. If you're on a network you don't control, you might need to fall back to IP addresses.

Check that Metro is actually listening on all interfaces:

lsof -i :8081

You should see node listening on *:8081 or 0.0.0.0:8081, not 127.0.0.1:8081. If it's only on localhost, your configuration isn't taking effect.

Router and Network Considerations

Some routers have "AP isolation" or "client isolation" enabled, which prevents devices on the network from communicating with each other. This is common on guest networks for security reasons. Check your router settings if Bonjour works on some networks but not others.

Dual-band routers sometimes treat the 2.4GHz and 5GHz bands as separate networks. Make sure your Mac and testing device are on the same band, or that your router bridges them properly.

VPNs can also interfere. If you're connected to a VPN on your Mac, local network traffic might be routed through the tunnel instead of staying local. Try disconnecting the VPN while testing.

Using IP Address as Fallback

If Bonjour just won't cooperate, you can always fall back to your Mac's IP address:

ipconfig getifaddr en0

This returns your WiFi IP address. Use it the same way as the Bonjour hostname:

REACT_NATIVE_PACKAGER_HOSTNAME=192.168.1.42 npx expo start

The downside is you'll need to update this when your IP changes, but it bypasses any mDNS issues.

Making It Persistent

Rather than setting environment variables every time, add them to your shell configuration. In ~/.zshrc:

export REACT_NATIVE_PACKAGER_HOSTNAME="$(scutil --get LocalHostName).local"

Now every terminal session automatically uses your Bonjour hostname for Metro.

For team projects where everyone has different hostnames, you might use a .env.local file that's gitignored:

REACT_NATIVE_PACKAGER_HOSTNAME=Micks-MacBook-Pro.local

Then load it in your start script or use a package like dotenv to read it.

subscribe.sh

// Stay Updated

Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.

>

By subscribing, you agree to our Privacy Policy.