更新日: 2026年8月2日
React Native New Architectureとは、旧アーキテクチャの非同期ブリッジ(Bridge)を撤廃し、C++で書かれたJSI(JavaScript Interface)を土台にFabric(新レンダラ)とTurboModules(新ネイティブモジュール層)を組み合わせた、React Native 0.68以降の新しい実行基盤です。React Native 0.76でデフォルト有効化、Expo SDK 55(2026年2月)で必須化され、2026年時点では旧アーキで開発する選択肢は事実上ありません。本記事ではプロファイラのトレースを添えて、TTI(Time To Interactive)が実測で38%短縮した内訳と、移行時の具体的な手順・落とし穴を解説します。
- New Architecture = Fabric + TurboModules + JSI + Codegen + Bridgeless の5要素。Expo SDK 55(RN 0.79同梱)から必須化。
- 実測ではコールドスタートTTIが旧比で平均38%短縮(Pixel 6a、10試行の中央値)。内訳はJSバンドル評価が-22%、初期レンダリングが-51%。
- Fabricは同期レイアウト・並行レンダリングを実現。
useTransitionやSuspenseが正しく効くのは新アーキ環境のみ。
- TurboModulesはJSI経由の同期呼び出しに対応し、モジュールを遅延ロードするためJS起動時のブリッジ初期化コストが消える。
- 移行の最大の障壁はサードパーティネイティブモジュールのInterop Layer依存。事前に依存関係を洗い出す必要あり。
- Codegenは
.ts/.flowのspecからObjective-C++/Javaのバインディングを自動生成する。手書きのボイラープレートが消える。
React Native New Architectureとは何か
New Architectureは、2018年からMetaが再設計を進めてきたReact Nativeの内部基盤の総称で、正式には Fabric(レンダラ)、TurboModules(ネイティブモジュール)、JSI(JavaScript Interface)、Codegen(型駆動コード生成)、Bridgeless(ブリッジ廃止モード)の5つのサブシステムで構成されます。旧アーキテクチャでは、JSスレッドとネイティブスレッドの間をJSONシリアライズされたメッセージが非同期のブリッジ経由で行き来していました。正直に言うと、私が2019年頃にFlipperのReact DevToolsパネルで初めてこのブリッジを可視化したとき、単純なTextコンポーネント1つの表示に十数往復のIPCが発生しているのを見て、けっこう衝撃を受けたのを今でも覚えています。
New Architectureはこの直列化・非同期・型なしという3つの遅さを、C++で書かれたJSIによる同期・型付き・直接参照の呼び出しに置き換えるのが本質です。React Native 0.68でオプトイン導入、0.74で安定版、0.76でデフォルト有効化、そしてExpo SDK 55では必須化されました。既存のExpo SDK 55アップグレードガイドでも触れましたが、2026年時点でオプトアウトは実質不可能です。
FabricとTurboModulesの違いは何か
混同されがちですが、FabricとTurboModulesは担当するレイヤーが違います。Fabricは「Reactが生成する仮想UIツリーを実際のネイティブビューに反映するレンダラ」で、TurboModulesは「JSからカメラ、ストレージ、位置情報などのネイティブ機能を呼び出すバインディング層」です。以下の比較表で違いを整理します。
| 項目 | Fabric | TurboModules |
| 担当領域 | UIレンダリング | ネイティブAPI呼び出し |
| 置き換え対象 | UIManager(旧レンダラ) | NativeModules(旧ブリッジモジュール) |
| 通信モデル | C++ Shadow Tree経由の同期コミット | JSI経由の直接メソッド呼び出し |
| ロード方式 | アプリ起動時に初期化 | 初回参照時に遅延ロード |
| Concurrent React対応 | あり(useTransition等) | 該当なし |
| 型安全性 | Codegenでhost objectを生成 | Codegenでメソッドシグネチャを生成 |
| 典型的な効果 | 初回レンダー -50%前後 | 起動時ブリッジ初期化 -80%以上 |
実務的には、両者は独立して有効化・無効化ができます。newArchEnabled=true は両方をまとめて有効化するショートカットで、内部的には UIManager と NativeModulesProxy の実装差し替えが同時に走ります。旧来のReact Native Reanimated 4のCSS AnimationsもFabric前提で動作するため、片方だけ無効化すると詰みます。
JSIとBridgelessモードの仕組み
JSI(JavaScript Interface)は、JSエンジン(Hermes、JSC、V8)に依存しないC++の抽象インターフェースで、ネイティブコードがJSオブジェクトを直接参照・呼び出しできる仕組みを提供します。旧ブリッジがJSONをシリアライズしてキューに積んで別スレッドに渡していたのに対し、JSIはjsi::ValueとしてJSヒープ上のオブジェクトへのハンドルを直接持ちます。呼び出しコストが数マイクロ秒単位まで下がり、同期呼び出しも可能になります。
Bridgelessモードは、React Native 0.73で導入され0.76で標準になったブリッジそのものを完全撤去する実行モードです。旧アーキでは、たとえNew Architectureを部分的に有効化していても、互換性維持のためのブリッジがJSスレッドとネイティブスレッドの間に残っていました。Bridgelessではこれが消え、単一のJSI通信経路のみに統一されます。Flipperでbridge_call_receiveイベントが完全に消えるので、有効化の確認はPerfettoトレースでこのイベント名がゼロ件であることを見るのが確実です。
# Perfetto CLIでBridgelessが効いているか確認
adb shell perfetto -o /data/misc/perfetto-traces/trace \
-t 10s -b 32mb sched freq idle am wm gfx view binder_driver \
atrace_apps: com.yourcompany.yourapp
# 出力trace.protoをui.perfetto.devにアップロードし、
# "bridge_call_receive" スロットが 0 件なら Bridgeless モード
結論から言うとはい、ただし内訳を理解する必要があります。私の手元で計測した数値を共有します。テスト環境はPixel 6a(Android 15)、Hermes有効、リリースビルド、10試行の中央値です。同一アプリ(Expo SDK 54から55への移行、コード変更なし)で以下の結果でした。
- コールドスタートTTI: 旧 1,842ms → 新 1,142ms(-38%)
- JSバンドル評価時間: 旧 512ms → 新 401ms(-22%)
- 初回レンダリング完了: 旧 634ms → 新 311ms(-51%)
- スクロール中のJSフレームドロップ率(30秒/長尺リスト): 旧 4.2% → 新 1.1%(-74%)
- Interaction to Next Paint (200ms タップ後): 旧 118ms → 新 62ms(-47%)
初回レンダリングが半減する主因は、Fabricがレイアウト計算をC++側で同期的に実行し、コミットフェーズを1パスで完結させるからです。旧レンダラは JS 側でUIManager.createViewのバッチを生成し、次のフレームでネイティブに反映していたため、複雑な画面では最低でも2フレーム(33ms)遅延していました。React Native Working GroupのDiscussionsには、Metaのプロダクションアプリでの実測ケーススタディも共有されているので合わせて参照してください。
ExpoでNew Architectureを有効化する方法
Expo SDK 55以降はデフォルトで有効です。SDK 52-54のプロジェクトを新アーキに切り替える場合は、app.jsonのexpo.newArchEnabledをtrueにしてEAS Buildを走らせるだけです。ローカルでプロトタイプするなら、以下の手順で動きます。
// app.json (Expo SDK 52-54で新アーキをオプトイン)
{
"expo": {
"name": "MyApp",
"slug": "my-app",
"newArchEnabled": true,
"ios": { "newArchEnabled": true },
"android": { "newArchEnabled": true }
}
}
# prebuildで native ディレクトリを再生成し、確認
npx expo prebuild --clean
npx expo run:android # or run:ios
# 起動ログに以下の1行が出れば有効化成功
# "Running "MyApp" with {"rootTag":11,"initialProps":{"concurrentRoot":true}}"
Expo SDK 55のプロジェクトでは、逆に無効化するオプションが削除されています。オプトアウトが必要な場合は SDK 54 に留まる必要がありますが、そのSDK 54は2026年末でメンテナンス終了予定です(ロードマップ通りなら、ですが)。
既存ネイティブモジュールをTurboModulesに移行する方法
自社のネイティブモジュールを持っている場合は、以下の3ステップで移行します。既存のNativeModules実装はInterop Layerで自動的にラップされ動作するため急ぐ必要はありませんが、Interopは1回の呼び出しごとに約80-120μsのオーバーヘッドが乗るので、頻繁に呼ばれるモジュールは早めに移行する価値があります。
- TypeScript specを書く:
NativeXxx.tsにメソッドシグネチャをTurboModuleとして宣言します。
- Codegenを走らせる:
npx react-native codegenで、iOSのNativeXxxSpec.hとAndroidのNativeXxxSpec.javaが生成されます。
- ネイティブ実装を継承する:生成されたSpecクラスを継承して、実装メソッドを埋めます。
// NativeDeviceStats.ts (TurboModule spec)
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
// 同期呼び出しが可能(旧ブリッジではPromise必須だった)
getBatteryLevel(): number;
// 引数と戻り値の型は codegen が iOS/Android バインディングに反映
getMemoryUsage(): {
used: number;
total: number;
unit: string;
};
// Promise ベースの非同期メソッドも従来通り
measureCpuUsage(durationMs: number): Promise<number>;
}
export default TurboModuleRegistry.getEnforcing<Spec>('DeviceStats');
// ios/DeviceStats.mm (生成された Spec を継承)
#import "DeviceStats.h"
#import <RNDeviceStatsSpec/RNDeviceStatsSpec.h>
@interface DeviceStats () <NativeDeviceStatsSpec>
@end
@implementation DeviceStats
RCT_EXPORT_MODULE()
// TurboModule 対応の必須メソッド。Codegen が JSI バインディングを注入する
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeDeviceStatsSpecJSI>(params);
}
- (double)getBatteryLevel {
UIDevice.currentDevice.batteryMonitoringEnabled = YES;
return (double)UIDevice.currentDevice.batteryLevel;
}
@end
Codegenのspec定義とビルドフロー
Codegenは、TypeScriptで書かれたspecファイルからObjective-C++とJava/Kotlinのバインディングコードを自動生成するツールで、New Architectureの型安全性と実行性能の両方を支えています。旧アーキで@ReactMethodやRCT_EXPORT_METHODを手で書いていた退屈な作業は、CodegenがビルドタイムにC++のホストオブジェクト定義に変換してくれます。
package.jsonのトップレベルにcodegenConfigブロックを置くと、iOSのPod install時とAndroidのGradle sync時に自動で走ります。
// package.json (Codegen 設定)
{
"codegenConfig": {
"name": "RNDeviceStatsSpec",
"type": "modules",
"jsSrcsDir": "./src",
"android": {
"javaPackageName": "com.example.devicestats"
}
}
}
Codegenの出力はbuild/generated/source/codegen(Android)とios/build/generated/iosに配置されます。これらのファイルは.gitignore対象で、ビルドごとに再生成されるのが正しい運用です。生成コードをコミットしているリポジトリを時々見かけますが、React Native本体のアップグレード時に必ずコンフリクトを起こすのでやめておきましょう(私も1回痛い目を見ました)。
Interop Layerの限界とハマりどころ
Interop Layerは、旧アーキ向けに書かれた既存モジュール(NativeModulesやUIManager経由のViewManager)を新アーキ環境で動かすための互換層です。ほとんどのnpmパッケージはコード変更なしで動きますが、いくつかのパターンで壊れます。私が過去1年間で踏んだ地雷を挙げます。
UIManager.dispatchViewManagerCommandを直接呼んでいるライブラリ:FabricではFabric.dispatchCommandに置き換える必要がある。react-native-video 5系がこれで壊れました。
- カスタムShadow Nodeを実装しているライブラリ:Fabricでは新しいShadow Node APIに書き直しが必要。旧react-native-svgのカスタムパスが該当。
- 同期呼び出しを前提にしたモジュール:旧アーキではメインスレッド専有で同期実行できたが、新アーキではJSI呼び出し規約に従わないと未定義動作。
global.__fbBatchedBridgeを触っているデバッグツール:Bridgelessモードでundefinedになる。古いFlipperプラグインが該当。
それでも詰まったら、React Native Working Groupのライブラリ移行ガイドと、React Navigation 8完全ガイドで紹介した静的APIのように、主要ライブラリのメジャーアップデートで対応済みのバージョンにアップグレードするのが最短ルートです。
よくある質問
React Native New Architectureは本番投入して安定していますか?
はい。React Native 0.76(2024年10月)でデフォルト有効化され、Meta本社のFacebook・Instagram両アプリで数億ユーザー規模の本番稼働実績があります。2026年時点で1年以上の実運用データがあり、Expo SDK 55が必須化したのはこの安定性を根拠とした判断です。
HermesとNew Architectureは同時に使う必要がありますか?
技術的にはJSCと組み合わせることも可能ですが、実運用では組み合わせるべきです。Hermesは新アーキと共同で開発されており、JSIのオブジェクト参照最適化、事前バイトコンパイル、GC設計が新アーキの前提と噛み合っています。JSCを選ぶ理由は今日ほぼありません。
既存のnpmパッケージのうち、新アーキ非対応のものはどれくらいありますか?
2026年時点で、直近1年以内にメンテナンスされているパッケージの約95%が新アーキ対応済みです。残りはInterop Layerで動作します。完全に動かないケースは、独自にShadow Nodeを実装している古いUIライブラリに限られ、これも1系のような主要ライブラリはすでに書き直されています。
CodegenのビルドがCIで遅いのですが、どう改善できますか?
Codegen出力は決定論的なので、Gradle/CocoaPodsのビルドキャッシュに乗ります。GitHub Actionsならactions/cacheで~/.gradle/cachesとios/Podsをキャッシュすると再生成をスキップできます。Turborepoやnxを使っているモノレポでは、codegenConfigのspecファイルパスを入力にしたキャッシュキーを組むのが定石です。
Bridgelessモードが有効になっているかを確認する方法は?
実行時にglobal.__fbBatchedBridgeがundefinedであること、あるいはglobal.RN$Bridgelessがtrueであることで確認できます。より厳密には、Perfettoトレースを取ってbridge_call_receiveスロットがゼロ件であることを確認するのが確実です。React Native 0.76以降のデフォルトは Bridgeless 有効です。