Nitro Modules у React Native 2026: створення нативних модулів на Swift, Kotlin і C++

Практичний посібник з Nitro Modules у React Native 2026: як створити нативний модуль на Swift і Kotlin, згенерувати type-safe біндинги через Nitrogen і коли обирати Nitro замість Turbo Modules.

Nitro Modules React Native Guide 2026

Оновлено: 11 серпня 2026 р.

Nitro Modules — це фреймворк для створення швидких type-safe нативних модулів у React Native зі статично скомпільованими JSI-біндингами, який працює поверх Turbo Modules і забезпечує пряму взаємодію Swift ↔ C++ (без Objective-C) та Kotlin через fbjni. У 2026 році Nitro став еталоном для сценаріїв, де кожна мілісекунда мостового виклику має значення: у бенчмарку 100 000 нативних викликів Nitro показує близько 7 мс проти 115 мс у Turbo Modules та 434 мс у Expo Modules. Нижче розберемо, як побудувати такий модуль з нуля, коли він виправданий і як інтегрувати його в монорепо з єдиним типовим контрактом на TypeScript.

  • Nitro Modules генерують C++/Swift/Kotlin-код зі специфікацій *.nitro.ts через nitrogen, повністю усуваючи розбіжність типів між JS і нативом.
  • Продуктивність вища за Turbo Modules у ~15–60 разів на throughput-навантаженнях завдяки прямому Swift ↔ C++ interop і jsi::NativeState замість HostObject.
  • Hybrid Object: фундаментальна одиниця Nitro, тобто об'єкт з методами та властивостями, який реалізується в одній із трьох мов і викликається з JS без обгорток.
  • Nitrogen залишається опціональним CLI: без нього ви реєструєте Hybrid Objects вручну через HybridObjectRegistry.
  • Turbo Modules залишаються офіційним стандартом React Native Core; Nitro підходить для performance-critical бібліотек (камера, ML, шифрування, аудіо).
  • Async-методи в Swift використовують нативний async/await, у Kotlin працюють через корутини, а в C++ через Promise::async(); помилки прокидаються як JS-винятки автоматично.

Що таке Nitro Modules і як вони працюють

Nitro Modules — це надбудова над JSI, розроблена Marc Rousavy (автор react-native-vision-camera), яка дозволяє писати нативні модулі напряму на Swift, Kotlin або C++ і викликати їх із JavaScript синхронно, без бриджа й без серіалізації JSON. Технічно Nitro будується поверх тієї ж інфраструктури, що й Turbo Modules: JSI-runtime і TurboModuleRegistry. Різниця у філософії: якщо Turbo Modules оптимізують універсальність (Objective-C для iOS, Java для Android), то Nitro оптимізує мінімум оверхеду виклику і type-safety на етапі компіляції.

Ключова одиниця Nitro називається Hybrid Object. Це нативний об'єкт із методами та властивостями (гетер/сетер), реалізований у C++, Swift чи Kotlin і зареєстрований у HybridObjectRegistry. З боку JavaScript такий об'єкт виглядає й поводиться як звичайний ES-об'єкт: підтримує примітиви, типізовані й нетипізовані об'єкти, масиви, варіанти та інші нативні об'єкти як параметри й повертані значення.

Другий стовп, генератор коду Nitrogen, парсить файли з розширенням .nitro.ts, читає ваші TypeScript-інтерфейси й генерує статично типізовані C++ шаблонні контракти, а заодно Swift-протоколи та Kotlin-інтерфейси. Якщо на нативному боці ви забудете реалізувати метод або повернете неправильний тип, модуль просто не скомпілюється. Це та сама модель «TS-first», яку архітектори полюбили за GraphQL Codegen і Prisma: єдине джерело істини у вигляді TypeScript-контракту.

Під капотом Nitro використовує jsi::NativeState замість типового jsi::HostObject. Це дає кращу інтеграцію з JS-прототипами й точніший контроль над життєвим циклом об'єктів у V8/Hermes GC. JS ↔ C++ конвертери типів генеруються заздалегідь через constexpr-шаблони, тому в рантаймі відсутні динамічні лукапи та парсинг сигнатур.

Nitro Modules vs Turbo Modules vs Expo Modules: порівняння 2026

Питання «що обрати» стало практичним у 2026, коли React Native 0.79 стабілізував нову архітектуру за замовчуванням. Ось порівняння трьох активних підходів, зведене за офіційною таблицею Margelo та бенчмарками з нашого досвіду у fintech-монорепо на 40+ бібліотек.

КритерійTurbo ModulesNitro ModulesExpo Modules
Підтримка в ядрі RNТак (офіційно)Ні (Margelo, npm)Ні (Expo)
Мови для iOSObjective-C, Objective-C++, C++Swift, C++ (без Obj-C)Swift
Мови для AndroidJava, Kotlin, C++Kotlin, C++ (через fbjni)Kotlin
Codegen з TSCodegen (build-time)Nitrogen (pre-shipped, в npm-пакеті)Runtime introspection
Throughput (100k викликів)~115 мс~7 мс~434 мс
BoilerplateВисокийМінімальнийНизький
Type-safety native ↔ JSНа рівні CodegenCompile-time (шаблони C++)Runtime перевірки
Роздільна збірка (bridgeless)ТакТакТак
Найкращий сценарійСтабільні прод-модулі, широка сумісністьКамера, ML, крипто, аудіоDX-first Expo-модулі

Чесно кажучи, числа 7 мс vs 115 мс vs 434 мс, це throughput у синтетичному тесті, а не реальна різниця у користувацькому досвіді. Але коли ваш модуль викликається на кожен кадр (наприклад, обробка frame processor у камері або аудіо-фільтр), різниця у 15× стає видимою в jank-статистиці. Для більшості CRUD-подібних модулів (доступ до Keychain, читання конфігу) різниці ви не помітите.

Встановлення react-native-nitro-modules та Nitrogen

Nitro складається з двох npm-пакетів. Пакет react-native-nitro-modules є C++ ядром, яке має бути залежністю кожної Nitro-бібліотеки та кожного застосунку, що її використовує. А nitrogen, у свою чергу, це CLI-генератор коду, який потрібен лише авторам бібліотек, а не кінцевим користувачам.

# У застосунку (споживач)
npm install react-native-nitro-modules
cd ios && pod install

# У бібліотеці (автор модуля)
npm install --save-peer react-native-nitro-modules
npm install --save-dev nitrogen

Перед першою збіркою переконайтеся, що ваш проєкт працює з новою архітектурою: у Podfile має бути ENV['RCT_NEW_ARCH_ENABLED'] = '1', а в android/gradle.properties рядок newArchEnabled=true. Для React Native 0.79+ це вже за замовчуванням. Якщо ви ще на старій архітектурі, спершу пройдіть міграцію. Про підводні камені написано в нашому матеріалі про оптимізацію продуктивності React Native з новою архітектурою та React Compiler.

Створіть у корені бібліотеки файл nitro.json, це маніфест, який Nitrogen читає для розуміння структури проєкту:

{
  "cxxNamespace": ["margelo", "nitro", "math"],
  "ios": {
    "iosModuleName": "NitroMath"
  },
  "android": {
    "androidNamespace": ["margelo", "nitro", "math"],
    "androidCxxLibName": "NitroMath"
  },
  "autolinking": {
    "Math": {
      "swift": "HybridMath",
      "kotlin": "HybridMath"
    }
  }
}

Розділ autolinking, це саме те, що зв'язує TypeScript-ім'я "Math" з класами HybridMath.swift і HybridMath.kt. Без цього мапінгу Nitro не знатиме, який нативний клас створювати при виклику NitroModules.createHybridObject<Math>("Math").

Type-safe специфікації через *.nitro.ts

Уся публічна поверхня Nitro-модуля описується TypeScript-інтерфейсами у файлах *.nitro.ts. Це той самий підхід, що й у Protocol Buffers, тільки безсхемний і без окремого DSL. Створіть src/Math.nitro.ts:

import type { HybridObject } from 'react-native-nitro-modules'

export interface Vector {
  x: number
  y: number
  z: number
}

export interface Math extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
  // Синхронний метод
  add(a: number, b: number): number

  // Властивість (гетер/сетер)
  pi: number

  // Робота з об'єктами
  normalize(v: Vector): Vector

  // Асинхронний метод (повертає Promise)
  heavyCompute(iterations: number): Promise<number>
}

Тип-параметр HybridObject<{ ios: 'swift'; android: 'kotlin' }> вказує Nitrogen, які нативні мови генерувати. Якщо ви обираєте 'c++' для обох платформ, Nitrogen створить єдину крос-платформну C++ реалізацію без Swift- і Kotlin-обгорток. Тепер запустіть генератор:

npx nitro-codegen
# або, якщо додали script:
npm run specs

Nitrogen створить директорію nitrogen/generated/ з трьома підпапками: shared/ (C++ контракт), ios/ (Swift-протокол HybridMathSpec) і android/ (Kotlin-інтерфейс HybridMathSpec). Ці файли треба комітити в git і включати в npm-пакет, щоб споживачі бібліотеки не мусили запускати Nitrogen самі.

Реалізація Hybrid Object на Swift для iOS

Nitrogen згенерує Swift-протокол HybridMathSpec у nitrogen/generated/ios/swift/. Ваше завдання полягає у створенні класу, що його реалізує. Створіть ios/HybridMath.swift:

import Foundation
import NitroModules

final class HybridMath: HybridMathSpec {
    // Обов'язкові Nitro-властивості з базового класу
    var hybridContext = margelo.nitro.HybridContext()
    var memorySize: Int { 0 }

    // Реалізація властивості з TS-інтерфейсу
    var pi: Double = 3.141592653589793

    func add(a: Double, b: Double) throws -> Double {
        return a + b
    }

    func normalize(v: Vector) throws -> Vector {
        let length = (v.x * v.x + v.y * v.y + v.z * v.z).squareRoot()
        guard length > 0 else {
            throw RuntimeError.error(withMessage: "Cannot normalize zero vector")
        }
        return Vector(x: v.x / length, y: v.y / length, z: v.z / length)
    }

    func heavyCompute(iterations: Double) throws -> Promise<Double> {
        return Promise.async {
            var acc: Double = 0
            for i in 0..<Int(iterations) {
                acc += Double(i).squareRoot()
            }
            return acc
        }
    }
}

Кілька важливих деталей. По-перше, Nitro напряму мапить TS number у Swift Double, і це стандартна поведінка JS-Number як 64-bit float. По-друге, кожен метод може кидати помилку через throws, і Nitro автоматично перетворить її на JS-виняток на боці createHybridObject. По-третє, для async-роботи використовується Promise.async { ... }, який під капотом запускає замикання на власному thread pool Nitro й повертає результат у JSI-runtime.

Оскільки Nitro бридиться до Swift напряму через новий Swift ↔ C++ interop (без Objective-C), вам не потрібно писати @objc-обгортки, ані @objcMembers. Це знімає цілий шар мертвого коду й типових багів на кшталт «Objective-C method conflict». Для проєктів, що вже пишуть UI-компоненти на Swift, це особливо приємно, адже тепер і бізнес-логіка модулів у тому ж синтаксисі.

Реалізація Nitro Module на Kotlin для Android

Для Android Nitrogen створює Kotlin-інтерфейс. Реалізуйте його в android/src/main/java/com/margelo/nitro/math/HybridMath.kt:

package com.margelo.nitro.math

import com.margelo.nitro.core.Promise
import kotlin.math.sqrt

class HybridMath : HybridMathSpec() {
    override var pi: Double = 3.141592653589793

    override fun add(a: Double, b: Double): Double {
        return a + b
    }

    override fun normalize(v: Vector): Vector {
        val length = sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
        require(length > 0) { "Cannot normalize zero vector" }
        return Vector(v.x / length, v.y / length, v.z / length)
    }

    override fun heavyCompute(iterations: Double): Promise<Double> {
        return Promise.async {
            var acc = 0.0
            for (i in 0 until iterations.toInt()) {
                acc += sqrt(i.toDouble())
            }
            acc
        }
    }
}

Nitro на Android використовує fbjni, той самий міст, який Facebook застосовує в React Native Core для швидкого JNI-виклику. Promise.async у Kotlin запускає корутину на диспетчері за замовчуванням; у продакшн-модулях підмінюйте його на Dispatchers.IO для мережевих операцій або Dispatchers.Default для CPU-bound.

Останній крок: зареєструвати модуль у Java-пакеті React Native. У згенерованому MathOnLoad.kt вже є виклик HybridObjectRegistry.registerHybridObjectConstructor("Math") { HybridMath() }. Ваш *Package.kt має викликати MathOnLoad.initializeNative() у onLoad()-хуку JNI:

class MathPackage : TurboReactPackage() {
    companion object {
        init {
            System.loadLibrary("NitroMath")
            MathOnLoad.initializeNative()
        }
    }
    override fun getModule(name: String, ctx: ReactApplicationContext) = null
    override fun getReactModuleInfoProvider() = ReactModuleInfoProvider { emptyMap() }
}

Async/await, проміси та обробка помилок

Асинхронна модель Nitro узгоджена між усіма трьома мовами. У JS ви завжди отримуєте Promise; у Swift/Kotlin він створюється через Promise.async, а у C++ через Promise<T>::async(...). Nitro сам виконає лямбду на потоці зі свого пулу й повторно потрапить у JSI-runtime для резолву. Це важлива відмінність від Turbo Modules, де асинхронна модель залежить від того, чи метод повертає Promise у Codegen-специфікації, і де треба вручну керувати CallInvoker.

// Використання з JS/TS
import { NitroModules } from 'react-native-nitro-modules'
import type { Math } from './specs/Math.nitro'

const math = NitroModules.createHybridObject<Math>('Math')

// Синхронний виклик, виконується у поточному тику
const sum = math.add(2, 3)

// Асинхронний, офлоадиться на нативний потік
try {
  const result = await math.heavyCompute(1_000_000)
  console.log('Result:', result)
} catch (err) {
  // Помилки із throws / require перетворюються на JS Error
  console.error('Nitro error:', err)
}

Помилки прокидаються прозоро: throw RuntimeError.error(withMessage:) у Swift, require(...) { "message" } або throw IllegalStateException("...") у Kotlin, throw std::runtime_error("...") у C++. Усі вони стають Error-об'єктами у JS зі збереженим стеком (наскільки це можливо через міст). Я у своєму продакшн-коді дописую middleware над Nitro-обгорткою, який ловить винятки й репортить у Sentry з нативним stack trace. Деталі є в нашому гайді про зневадження React Native з DevTools, Reactotron та Hermes Profiler.

Інтеграція в монорепо та CI

У платформних командах Nitro-модулі часто живуть у монорепо поруч із дюжиною інших бібліотек. Ось патерн, який добре себе показав у нашій продуктовій платформі (Nx + pnpm workspaces, 40+ пакетів). По-перше, react-native-nitro-modules оголошуємо як peer dependency у кожній бібліотеці. Це унеможливлює множинні копії ядра, які інакше поламають HybridObjectRegistry.

По-друге, згенеровані Nitrogen специфікації комітяться, але їх cache-key у CI прив'язуємо до хешу *.nitro.ts-файлів через turbo або nx. Це усуває необхідність перегенерації, якщо специфікації не змінилися. По-третє, ми тримаємо єдиний nitro.json-preset для монорепо (за іменами namespace), який імпортують окремі бібліотеки. Так у команді нема розбіжностей у cxx namespace, які пізніше виллються в конфлікти лінкера.

Для тестування Hybrid Object на JS-рівні використовуємо мок через NitroModules.createHybridObject-заглушку в jest.setup.ts. Це не замінює нативний unit-тест на Swift/Kotlin (для яких у нас окремі раннери XCTest та JUnit), але покриває бізнес-логіку React-компонентів, що споживають модуль. Якщо ви серйозно ставитеся до off-heap перформансу, покладіть у Nitro сховище стану, бо воно швидше за AsyncStorage і паралельно тестується так само, як MMKV у React Native з шифруванням та інтеграцією з Zustand.

Коли обирати Nitro, а коли Turbo Modules

Три роки на прод-моніторингах у fintech дали мені чіткі евристики. Nitro виправданий, коли справджується одне з трьох: (1) модуль викликається у гарячому шляху, як-от камера, обробка кадрів, реальний час звуку, потоковий крипто-хешинг; (2) вам критично потрібна повна type-safety на нативному боці, бо модуль пише команда без глибокої RN-експертизи; (3) ви пишете нову бібліотеку і хочете підтримувати Swift/Kotlin як first-class мови без Objective-C-глюкози.

Turbo Modules залишаються дефолтом для більшості випадків. Причини: (а) вони частина React Native Core, підтримуються Meta й екосистемою, тож жодного ризику застаріти; (б) якщо ви наслідуєте модуль від сторонньої бібліотеки, яка ще довго не мігрує на Nitro, простіше писати нову функціональність у тому ж стилі; (в) деякі проєкти на iOS досі тримають Objective-C++ як «мову довіри» для доступу до низькорівневих API, і там Turbo природніший.

Не рідше нас питають про сумісність із React Compiler і Reanimated 4. Nitro повністю сумісний з обома: React Compiler не аналізує JSI-об'єкти й не змінює семантику викликів, а Reanimated 4 працює на власному worklet-runtime, куди Nitro-об'єкти теж передаються (з відповідним маркером). Якщо цікавить, як ці шари поєднуються, у нас є окремий матеріал про Reanimated 4 у React Native.

Наприкінці кілька слів про ліцензію й екосистему. Nitro випускається під MIT-подібною ліцензією, живе в репозиторії mrousavy/nitro на GitHub, релізи виходять кожні кілька тижнів, версія на серпень 2026 має номер 0.30+. Він активно використовується у react-native-vision-camera, react-native-mmkv (частково) і десятках менших модулів. Для авторитетного посилання при внутрішніх аудитах я зазвичай прикладаю картку пакета react-native-nitro-modules на npm з тижневими завантаженнями та статусом підтримки.

Часті питання

Чим Nitro Modules відрізняються від Turbo Modules технічно?

Nitro будується поверх JSI (як і Turbo), але додає статично скомпільовані шаблонні C++ біндинги замість динамічної Codegen-логіки в рантаймі, підтримує прямий Swift ↔ C++ interop без Objective-C і використовує jsi::NativeState замість HostObject. Це дає ~15–60× меншу затримку на throughput-навантаженнях.

Чи можна використовувати Nitro Modules без Nitrogen?

Так. Nitrogen залишається опціональним CLI для авторів бібліотек. Без нього ви створюєте C++-класи, що успадковуються від HybridObject, і реєструєте їх вручну через HybridObjectRegistry::registerHybridObjectConstructor. Втрачаєте лише генерацію Swift/Kotlin-обгорток із TypeScript-специфікацій.

Чи підтримує Nitro стару архітектуру React Native?

Ні. Nitro вимагає нову архітектуру (Fabric + JSI) і не працює з legacy-бриджем. З React Native 0.79+ нова архітектура вмикається за замовчуванням, тож для більшості нових проєктів цього обмеження не помічаєте.

Чи можна змішувати Turbo Modules і Nitro Modules у одному застосунку?

Можна і навіть рекомендовано. Вони працюють поруч на тому ж JSI-runtime. Використовуйте Turbo для широкої сумісності й повільніших операцій (Keychain, deep links, налаштування), а Nitro тримайте для гарячих шляхів на кшталт обробки frame processor у камері або real-time аудіо.

Як тестувати Nitro Modules у Jest без нативної збірки?

У jest.setup.ts замокайте NitroModules.createHybridObject, повертаючи фейковий об'єкт із потрібними методами й властивостями. Для перевірки самих нативних реалізацій пишіть окремі XCTest (iOS) і JUnit-тести (Android). Nitro не заміняє нативне тестування бізнес-логіки на нативному боці.

Yelena Petrov
Про Автора Yelena Petrov

React Native architect at a fintech. Builds platform teams, type-safe bridges, and runs the upgrade playbook so others don't have to.