From 682bfc97456d856f3660d00027d204f672460018 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Thu, 20 Aug 2026 18:10:59 +0200 Subject: [PATCH] fix: Mark react-native-nitro-modules peer dependency as optional Semver ranges never match pre-releases, so a required peer of "*" does not match e.g. 0.37.0-beta.0. Package managers then install a second, stable copy of Nitro next to the pre-release, and the native/JS version guard throws "Nitro was installed twice" at runtime. Marking the peer optional stops the package manager from resolving Nitro on the library's behalf, leaving the app in full control of the version. --- bun.lock | 6 ++++ .../how-to-build-a-nitro-module.md | 19 ++++++++++++ docs/docs/guides/troubleshooting.md | 30 +++++++++++++++++++ .../package.json | 5 ++++ packages/react-native-nitro-test/package.json | 5 ++++ packages/template/package.json | 5 ++++ 6 files changed, 70 insertions(+) diff --git a/bun.lock b/bun.lock index b8b0a5f7a9..37fdf2a39e 100644 --- a/bun.lock +++ b/bun.lock @@ -147,6 +147,9 @@ "react-native-nitro-modules": "*", "react-native-nitro-test-external": "*", }, + "optionalPeers": [ + "react-native-nitro-modules", + ], }, "packages/react-native-nitro-test-external": { "name": "react-native-nitro-test-external", @@ -170,6 +173,9 @@ "react-native": "*", "react-native-nitro-modules": "*", }, + "optionalPeers": [ + "react-native-nitro-modules", + ], }, }, "packages": { diff --git a/docs/docs/getting-started/how-to-build-a-nitro-module.md b/docs/docs/getting-started/how-to-build-a-nitro-module.md index d9375f91b8..c7ab664de5 100644 --- a/docs/docs/getting-started/how-to-build-a-nitro-module.md +++ b/docs/docs/getting-started/how-to-build-a-nitro-module.md @@ -44,6 +44,25 @@ First, you need to create a [Nitro Module](../concepts/nitro-modules) - either b npm install nitrogen --save-dev ``` + Also declare `react-native-nitro-modules` as an **optional peer dependency**, so the app using your library is the one that decides which Nitro version gets installed: + + ```json title="package.json" + { + "peerDependencies": { + "react-native-nitro-modules": "*" + }, + "peerDependenciesMeta": { + "react-native-nitro-modules": { + "optional": true + } + } + } + ``` + + :::warning + The `optional` flag matters. Without it, package managers try to satisfy the peer range themselves - and since semver ranges never match pre-releases, `"*"` does **not** match a version like `0.37.0-beta.0`. A user testing a Nitro beta would then get a second, stable copy of `react-native-nitro-modules` installed next to yours, and Nitro throws [`Nitro was installed twice`](../guides/troubleshooting#nitro-was-installed-twice) at runtime. Marking the peer optional stops the package manager from installing Nitro on your behalf. + ::: + Then, you need to decide if you want to use Nitro's C++ library directly, or use [nitrogen](../concepts/nitrogen) to generate specs: diff --git a/docs/docs/guides/troubleshooting.md b/docs/docs/guides/troubleshooting.md index d2d5bc2af7..b470f6faca 100644 --- a/docs/docs/guides/troubleshooting.md +++ b/docs/docs/guides/troubleshooting.md @@ -66,6 +66,36 @@ If your app fails to build after installing Nitro or a library powered by Nitro, +## Nitro was installed twice + +If your app crashes at startup with: + +``` +Nitro was installed twice: once with native version X and once with JS version Y. +``` + +...then two different copies of `react-native-nitro-modules` ended up in your project - the native build linked one, and Metro bundled the other. + +The most common cause is **installing a Nitro pre-release** (e.g. `0.37.0-beta.0`). Semver ranges never match pre-release versions, so a library declaring `"react-native-nitro-modules": "*"` as a required peer dependency does not consider the beta a match - and your package manager silently installs a second, stable copy inside that library's `node_modules` to satisfy the peer. + +To fix it: + +1. Check how many copies you have: + ```sh + npm ls react-native-nitro-modules + ``` +2. If a library nests its own copy, ask the library author to mark the peer dependency as [optional](../getting-started/how-to-build-a-nitro-module#11-install-nitro-and-nitrogen). +3. As a workaround until then, force a single version from your app's `package.json`: + ```json title="package.json" + { + "overrides": { + "react-native-nitro-modules": "0.37.0-beta.0" + } + } + ``` + (use `resolutions` instead of `overrides` for Yarn) +4. Delete `node_modules` and reinstall - package managers often leave the stale nested copy on disk even after the lockfile is fixed. + ## Runtime error If your app crashes at runtime, make sure to inspect the native logs. diff --git a/packages/react-native-nitro-test-external/package.json b/packages/react-native-nitro-test-external/package.json index 6d729977b3..6fb1768c2f 100644 --- a/packages/react-native-nitro-test-external/package.json +++ b/packages/react-native-nitro-test-external/package.json @@ -71,6 +71,11 @@ "react-native": "*", "react-native-nitro-modules": "*" }, + "peerDependenciesMeta": { + "react-native-nitro-modules": { + "optional": true + } + }, "eslintConfig": { "root": true, "extends": [ diff --git a/packages/react-native-nitro-test/package.json b/packages/react-native-nitro-test/package.json index 59c290b19f..a48b9db8e9 100644 --- a/packages/react-native-nitro-test/package.json +++ b/packages/react-native-nitro-test/package.json @@ -72,6 +72,11 @@ "react-native-nitro-modules": "*", "react-native-nitro-test-external": "*" }, + "peerDependenciesMeta": { + "react-native-nitro-modules": { + "optional": true + } + }, "jest": { "preset": "@react-native/jest-preset", "modulePathIgnorePatterns": [ diff --git a/packages/template/package.json b/packages/template/package.json index 5a8c98ec8c..96658f7523 100644 --- a/packages/template/package.json +++ b/packages/template/package.json @@ -71,6 +71,11 @@ "react-native": "*", "react-native-nitro-modules": "*" }, + "peerDependenciesMeta": { + "react-native-nitro-modules": { + "optional": true + } + }, "eslintConfig": { "root": true, "extends": [