- 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.
// Continue_Learning
Fixing Port 8081 Already in Use: Orphaned Metro Bundler Processes
When Metro won't start because port 8081 is already in use, you likely have an orphaned process. Here's how to find and kill it properly.
Automatically Switch Node Versions with nvm for React Native Projects
Avoid cryptic build failures by setting up nvm to automatically use the right Node version when you cd into a project.
Using Vercel's Skills Library to Supercharge AI Agents for React Native
Vercel's Skills library lets you add reusable instruction sets to AI coding agents. Here's how to use it to make agents more effective for React Native development.
// Stay Updated
Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.