How to fix "No bundle URL present" and change the packager port in React Native

August 9, 2021 JavaScript React Native

If you work with more than one React Native project, running them concurrently becomes a problem. Alternatively, if port 8081 on your system is used by some other application, or blocked by a firewall, you might not be able to run React Native at all.

This usually precipitates the “No bundle URL present. Make sure you’re running a packager server or have included a .jsbundle file in your application bundle.” red error message.

Only the Make sure you’re running a packager server part pertains to local development. Bundle files are for deployment. So, what is going on?

When you start a RN app for local development, it runs in a sandboxed simulator. So how can it reload source code without recompiling the app? More or less, like a web app with webpack-dev-server — it relies on a packager server that will serve the code and assets over HTTP. React Native uses their own packager: Metro. Whenever you run a React Native app in development mode, a Metro server is started to package scripts.

Thus, the most common cause for the "No bundle URL present" error is that the packager server is not running - you don’t have a terminal window with a running metro process.

However, if you already started a metro process, it is ready and waiting, but the app is still showing the error, then either the port is blocked, or the metro that is running is from a different project than the app - each project requires a separate instance of metro.

Either way, this brings us to changing to a different port. By default, all React Native apps use the same port to communicate with Metro — 8081. It’s not exactly hardcoded, but changing the port is trickier than usual.

Change the Metro port

Firstly, you change the port that Metro would be running on. This is quite conventional. There are two options that both work equally well:

  1. EITHER add the following declaration to your metro.config.js:

    {
     server: {
       port: 31337;
     }
    }
    
  2. OR when starting the packager, pass port with react-native start --port 31337.

You can confirm that Metro is listening on the new port by opening http://localhost:31337 in a browser.

Change the port that the app will connect to

And secondly, the app should know to connect to a different port. Changing this is entirely unconventional.

You might find in Google that setting RCT_METRO_PORT will help, and – spoilers – it will. But what is RCT_METRO_PORT?

It looks like an environment variable, but setting the environment variable does nothing — no matter where you try to set it. Is the environment passed correctly to the build script? Maybe not. Shell environment doesn’t mix well with GUI apps. But it’s the wrong rabbit hole - RCT_METRO_PORT is decidedly not an environment variable.

Searching in RN source code reveals it’s a preprocessor constant, defined in RCTDefines.h. That makes sense: it’s the native code that must load the JavaScript bundle, so it must know what the port is. Setting the port should happen before the native code is compiled.

So you might try to open Xcode and override the define in your project’s build settings. But adding to your project’s build settings does nothing. The constant is not in your project - it’s in the CocoaPods project, specifically, in the React-Core pod. I didn’t find an API to override the port before sOf course, it’s bad practice to manually edit a third party dependency. But there is a way to automatically inject configuration into CocoaPods projects - actually, React Native does that a lot.

So, let’s add some code to the Podfile:

# find this line (in the post_install block)
react_native_post_install(installer)

# and add this:
installer.pods_project.targets
  .find { |t| t.name == 'React-Core' }
  .build_configurations
  .find { |c| c.name == 'Debug'}
  .build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)', 'RCT_METRO_PORT=31337']

Now do a pod install, recompile the application with react-native run, and you should see the "No bundle URL present" error go away.

(Sidenote: you can also change the bundler URL in the in-app developer menu (triggered by the d key.) But the menu won’t even open until you get rid of the red box error. So let me know if you see a way to make use of it.)

Conclusions; working in a team

TLDR: set port in Metro options, and set RCT_METRO_PORT in React-Core preprocessor definitions.

Although these changes are not the most obvious, they are simple and can be committed in version control, to be shared without your teammates. Or, if you can’t agree on a port, you can extract the setting into an environment variable.

Buy me a coffee Liked the post? Treat me to a coffee