React Native Runtimes
Get started

Metro setup

Wrap your Metro config, configure roots and the generated entry, and use runtime-specific bootstrap files.

The Metro plugin scans your source files for threaded components, scheduled runtime functions, and runtime-specific bootstrap files, then writes a small generated entry that secondary runtimes load.

metro.config.js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const { withThreadedRuntime } = require('@react-native-runtimes/core/metro');

const config = {};

module.exports = withThreadedRuntime(
  mergeConfig(getDefaultConfig(__dirname), config),
);

Options

OptionDefaultWhat it controls
roots['App.tsx', 'src']Files and folders scanned for OnRuntime, threadedComponent, runtimeFunction, and directive functions.
generatedDir.threaded-runtimeWhere the generated entry is written. Add this folder to .gitignore.
generatedEntryentry.jsFilename of the generated entry inside generatedDir.
projectRootMetro's projectRootBase directory for scanning and for generated ids.

Loading the generated entry

The generated entry registers component loaders, runtime functions, and the ThreadedRuntimeHost root. Require it at the top of index.js so it runs in every runtime:

index.js
require('./.threaded-runtime/entry');

Component registrations are lazy (() => require(...)), so this adds nothing to main-runtime startup. Loading it on main is required for the JSI function dispatch mechanism, which enables calling functions on secondary runtimes.

.gitignore

Always exclude the generated folder from version control:

.gitignore
.threaded-runtime/

Per-runtime bootstrap files

For startup code that should only run on a specific runtime, create a root-level file with the runtime's name:

index.background.ts

The Metro plugin discovers files matching index.<runtime>.ts in the project root and emits a static conditional require. The generated entry loads index.background.ts only inside runtimes whose name or kind is background.

index.background.ts
import { registerThreadedHeadlessTask } from '@react-native-runtimes/core';
import { business } from './src/businessStore';

registerThreadedHeadlessTask<{ reason: string }>(
  'business:refresh',
  async ({ payload }) => {
    await business.hydrate();
    await business.update(state => ({
      ...state,
      lastRefreshReason: payload.reason,
      refreshCount: state.refreshCount + 1,
    }));
  },
);

void business.hydrate();

Keep UI out of bootstrap files

Don't import React components or anything that would mount UI inside index.<runtime>.ts. Treat it as the runtime's bootstrap: register headless tasks, hydrate stores, start app-lifetime queues.

The file suffix must match the runtime name used at the native side. If your native prewarm uses a different name, use that name as the suffix:

ThreadedRuntime.prewarmBusinessRuntime(applicationContext, "sync-engine")
index.sync-engine.ts

Troubleshooting

A threaded component isn't being registered

Add its file to roots or place it under one of the existing roots. Files outside the configured roots are invisible to the plugin.

On this page

React Native Runtimes is built with by Margelo

We build fast and beautiful apps. Contact us at margelo.com for high-end consultancy services.

Let's talk