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.
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
| Option | Default | What it controls |
|---|---|---|
roots | ['App.tsx', 'src'] | Files and folders scanned for OnRuntime, threadedComponent, runtimeFunction, and directive functions. |
generatedDir | .threaded-runtime | Where the generated entry is written. Add this folder to .gitignore. |
generatedEntry | entry.js | Filename of the generated entry inside generatedDir. |
projectRoot | Metro's projectRoot | Base 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:
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:
.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.tsThe 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.
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.tsTroubleshooting
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.