Article

Flutter vs React Native 2025年最新比較と選定基準

高田晃太郎
Flutter vs React Native 2025年最新比較と選定基準

2024年末時点のGitHubスター数はFlutterが約16万、React Nativeが約11万で、どちらも広く使われる成熟フレームワークとして定着しています[1][2]。スターは採用の絶対指標ではありませんが、エコシステムの厚みやナレッジの蓄積を読み解く入口にはなります。技術面では、FlutterはImpeller(新しいGPUレンダラ)とDart 3により描画と実行時の安定性を磨き[3]、React Nativeは新アーキテクチャ(Fabric/TurboModules/JSI)とHermes標準化によりブリッジのオーバーヘッドを減らす方向へ再設計が進みました[4][5]。結果として、クロスプラットフォームは「どちらが速いか」だけでなく、どの制約下で安定して継続運用できるかの勝負に移行しています。CTOに必要なのは宗教論争ではなく、ロードマップ、チームのスキル曲線、運用リスク、そしてROIまでを見通した選定基準です。本稿は2025年初頭に公開されている情報と実行可能なコード例に基づき、アーキテクチャから実装、パフォーマンスと運用、事業価値の観点までを冷静に比較します。

注記:本稿の数値・仕様は引用元の公開情報に基づきます。仕様は随時更新されるため、意思決定前に公式ドキュメントを必ず再確認してください。

2025年の技術前提:アーキテクチャとエコシステム

FlutterはSkia/Impellerによる独自レンダリングとDartのAOT(Ahead-of-Time、ビルド時にネイティブコード化)を軸に、プラットフォーム標準のUIコンポーネントに依存せず画面を直接描画します[9][10]。これによりUIの一貫性とフレーム整合性(フレーム落ちの少なさ)を得やすく、Material 3やカスタムデザインの忠実再現に強みがあります。ImpellerはiOSでデフォルト化され、フレームドロップの体感改善が広く共有されています[3]。対してReact NativeはReactの宣言的UIをJSランタイムで駆動し、新アーキテクチャ(Fabric/JSI/TurboModules。JSIはC++経由でJSとネイティブを直接結ぶ層)によって従来のブリッジのボトルネックを解消する方向へ進化しました[11][4]。Hermes(軽量JSエンジン)の同梱により、起動時間とメモリ特性の安定化が進んでいます[5]。ExpoやReact Native CLIの整備で、開発〜配布の体験は数年で大きく改善しました[6]。

エコシステムの観点では、Flutterは豊富な公式ウィジェットと安定したレンダリングがコア資産で、モバイルに加えWeb/デスクトップも公式にカバーします[7]。React NativeはWebをReact Native WebやExpoで補完し、モバイル二大OSに集中して磨き込む傾向です[8]。ピクセルパーフェクトな独自UIを短期間で届けたいならFlutter、既存のReact/TypeScript資産や採用市場の厚みを最大活用したいならReact Native、というのが2025年時点の実務的な見立てです。

アーキテクチャ差分が与える実務インパクト

描画経路とコンパイル戦略は、起動時間、入力レイテンシ、アニメーションの滑らかさ、モジュール化のしやすさに直結します。FlutterはAOTによりリリース時のTTI(Time to Interactive。ユーザーが意味ある操作を開始できるまでの時間)で優位に出るケースが多く、複雑なカスタムアニメーションでも一貫した見た目を担保しやすい傾向があります[10]。React Nativeは新アーキテクチャ下でJSIによる同期・準同期のやり取りが可能になり、従来のブリッジ起因のシリアライズやキュー詰まりが緩和されました[4][11]。既存ネイティブSDKとの連携、Reactの状態管理・レンダリング戦略の横展開は、Webとモバイルを横断する組織最適に効きます。

プラットフォームカバレッジと将来性

Flutterは単一コードベースでiOS/Android/Web/Windows/macOS/Linuxを公式サポートし[7]、Figma→FlutterのハンドオフやMaterial 3準拠デザインの実装速度が高いのが特色です。React NativeはiOS/Androidにフォーカスし、Webは補助ツールでカバーする戦略ですが、Expo Application Services(EAS)などの配布・OTA(Over-the-Air、ストア申請なしの差分配信)アップデートの運用効率が選ばれる理由になっています[6]。将来性は両者とも強く、FlutterはImpellerのAndroid適用範囲拡大や安定性改善を継続的に掲げ[16]、React Nativeは新アーキテクチャの定着とライブラリ移行の完了が引き続きのテーマです[17]。

実装で見る違い:UI、状態、ネイティブ連携、CI

同じ要件を実装したときの体験差を、短いコードで確認します。まずは最小のUIからです。

UIの骨格と開発体験

Flutterの基本的な構造はDartで完結し、ウィジェットツリーがそのままUI宣言になります。

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});
  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int count = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter (Flutter)')),
      body: Center(child: Text('$count', style: Theme.of(context).textTheme.displayLarge)),
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState(() => count++),
        child: const Icon(Icons.add),
      ),
    );
  }
}

React NativeはReactのコンポーネントモデルをそのまま用い、JS/TSで記述します。新アーキテクチャの有無はこのレイヤーでは大きく変わりません。

import React, {useState} from 'react';
import {SafeAreaView, Text, Pressable, StyleSheet} from 'react-native';

export default function App() {
  const [count, setCount] = useState(0);
  return (
    <SafeAreaView style={styles.container}>
      <Text style={styles.title}>Counter (React Native)</Text>
      <Text style={styles.value}>{count}</Text>
      <Pressable onPress={() => setCount(count + 1)} style={styles.btn}>
        <Text style={styles.btnLabel}>+1</Text>
      </Pressable>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {flex: 1, alignItems: 'center', justifyContent: 'center'},
  title: {fontSize: 18, marginBottom: 12},
  value: {fontSize: 56, marginBottom: 16},
  btn: {paddingHorizontal: 24, paddingVertical: 12, backgroundColor: '#4f46e5', borderRadius: 8},
  btnLabel: {color: '#fff', fontWeight: '600'},
});

UIの実装速度はチームの親和性に強く依存します。React/TypeScriptに慣れたフロントエンド比率が高い組織ではReact Nativeの学習コストが低く、独自UIの要求とデザイナーとの密連携が強い組織ではFlutterの一貫性が効きます。

ネイティブ連携(デバイス情報取得の最小例)

FlutterはPlatform Channel(Dartとネイティブ間のメッセージング)でつなぎます[12]。Dart側は以下のように簡潔です。

import 'package:flutter/services.dart';

const _channel = MethodChannel('com.example/device');

Future<String> getDeviceName() async {
  final name = await _channel.invokeMethod<String>('deviceName');
  return name ?? 'unknown';
}

Android側のKotlin実装はエンジン登録時にチャンネルを生やします。

package com.example.app

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import android.os.Build

class MainActivity: FlutterActivity() {
  override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.example/device")
      .setMethodCallHandler { call, result ->
        if (call.method == "deviceName") {
          result.success("${Build.MANUFACTURER} ${Build.MODEL}")
        } else {
          result.notImplemented()
        }
      }
  }
}

iOS(Swift)も同様にハンドラを登録します。

import UIKit
import Flutter

@UIApplicationMain
class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    let controller = window?.rootViewController as! FlutterViewController
    let channel = FlutterMethodChannel(name: "com.example/device", binaryMessenger: controller.binaryMessenger)
    channel.setMethodCallHandler { call, result in
      if call.method == "deviceName" {
        result(UIDevice.current.name)
      } else {
        result(FlutterMethodNotImplemented)
      }
    }
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

React NativeはNativeModules経由で呼び出します[13]。JS/TS側は次のように扱えます。

import {NativeModules} from 'react-native';

const {DeviceModule} = NativeModules as {DeviceModule: {getDeviceName: () => Promise<string>}};

export async function getDeviceName(): Promise<string> {
  return DeviceModule.getDeviceName();
}

Android側は最小のKotlinモジュールで対応できます。

package com.example.devicemodule

import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
import android.os.Build

class DeviceModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
  override fun getName() = "DeviceModule"

  @ReactMethod
  fun getDeviceName(promise: Promise) {
    promise.resolve("${Build.MANUFACTURER} ${Build.MODEL}")
  }
}

class DevicePackage : ReactPackage {
  override fun createNativeModules(reactContext: ReactApplicationContext): MutableList<NativeModule> =
    mutableListOf(DeviceModule(reactContext))

  override fun createViewManagers(reactContext: ReactApplicationContext): MutableList<ViewManager<*, *>> =
    mutableListOf()
}

両者ともにネイティブ連携の難度は大きく下がっていますが、Flutterは描画系まで独自で閉じる設計のためUI層の一貫性が得やすく、React Nativeは既存のネイティブSDKやWeb由来の状態管理を転用しやすいという性質の違いが残ります。複雑なセンサー制御や既存ネイティブ資産の再利用が主題であればReact Nativeが有利になりやすく、UIを握って差別化したい強いデザイン要求ではFlutterが工数と品質の両立を実現しやすいでしょう。

性能計測とチューニング視点

Flutterではフレームタイミング(ビルド処理とラスタライズの所要時間)を簡単に収集できます[14]。ビルド時間・ラスタ時間の偏りを見てボトルネックを切り分けるのが第一歩です。

import 'dart:developer';
import 'package:flutter/widgets.dart';
import 'package:flutter/scheduler.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  SchedulerBinding.instance.addTimingsCallback((timings) {
    for (final t in timings) {
      log('build=${t.buildDuration.inMilliseconds}ms raster=${t.rasterDuration.inMilliseconds}ms');
    }
  });
  runApp(const Placeholder());
}

React Nativeではインタラクション完了後をTTIの簡易指標とし、InteractionManagerでログを採ります[15]。Hermes利用時はメモリと起動ログも併せて確認します[5]。

import {useEffect} from 'react';
import {InteractionManager} from 'react-native';

export function useTTIProbe() {
  useEffect(() => {
    const start = Date.now();
    const task = InteractionManager.runAfterInteractions(() => {
      const tti = Date.now() - start;
      console.log(`TTI≈${tti}ms`);
    });
    return () => task.cancel();
  }, []);
}

計測の原則は同じで、起動時間、初回描画、入力レイテンシ、ジャンク発生頻度、メモリピークの五点を継続的に追うと意思決定に耐えるデータになります。FlutterはAOTとImpellerでフレーム整合性の再現性が高く、React Nativeは新アーキテクチャによりブリッジ混雑の再現性が改善され、チューニングの効きが読みやすくなりました[3][4][10]。ベンチマークは端末ごとの差が大きいため、対象OS・端末・ビルド設定を固定した上で比較することが重要です。

CI/CDと運用

Flutterは公式CLIがビルドまで巻き取りやすく、マルチターゲットをGitHub Actionsで一気に固められます。

name: flutter_ci
on: [push]
jobs:
  build:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with:
          channel: stable
      - run: flutter pub get
      - run: flutter test --machine
      - run: flutter build ipa --no-codesign
      - run: flutter build apk --release

React NativeはEAS(Expo Application Services)やfastlaneとの相性がよく、OTA配信の戦略設計が鍵になります[6]。JS/アセット差分を小刻みに配布し、ネイティブ変更が必要なタイミングだけストア申請に切り替えると、リードタイム短縮とリスク分散の両立がしやすくなります。

選定基準:要件と組織の文脈で決める

最も重要なのは、技術的な優劣ではなく事業要件×組織ケイパビリティの適合です。UI表現が競争優位の中心にあり、アニメーションや独自レンダリングで体験を握りたい場合、Flutterは設計の純度の高さが武器になります。Webフロントエンド人材が厚く、バックエンド/フロント/モバイルの型を横断して動かす体制であれば、Reactと知識の再利用が効くReact Nativeが全体最適になりやすいでしょう。

デバイス固有機能の深い統合や既存ネイティブSDKの資産を保ちたい現場では、React Nativeの新アーキテクチャ上でのブリッジ設計が読みやすく、段階的移行に向いています[4][17]。逆に、デザインシステムの厳格な適用や、プラットフォーム差異を越えたピクセル単位の再現がKPIに直結するなら、Flutterは依存の少ない描画経路が障害要因を減らします。採用市場という現実も無視できません。Stack Overflow Developer Survey 2024でもReactは上位に位置しており[18]、短期の採用速度ではReact Nativeが優位に出る地域が多い一方で、Flutterは少数精鋭での立ち上げと品質維持で逆転しやすい構図もあります。

シナリオ別の意思決定ヒント

フィンテックやヘルスケアのように規制準拠とパフォーマンスの安定が最優先で、UIもブランド一貫性が強く求められるケースでは、Flutterのレンダリング一貫性はリスク低減に寄与します。ECやコミュニティ系で施策頻度が高く、A/Bやフィーチャーフラグ、OTAでの素早い検証が売上に直結するケースでは、React NativeとEASの組み合わせが運用速度を押し上げます[6]。既存のネイティブアプリを段階的にモジュール置換したい場合、React Nativeは画面単位のインクリメンタル導入が比較的やりやすく、Flutterは新規プロダクトや大規模リニューアルで威力を発揮します。

ROIとリスク管理:数字で腹落ちさせる

投資対効果は、開発初期のスピードだけでなく、二年目以降の保守コストで差が開きます。FlutterはUIバグの回帰源が限定されやすく、描画層の差分がOSアップデートの影響を受けにくい反面、プラットフォーム固有UIの完全再現やWeb同等のアクセシビリティ要件では追加工数が発生しやすい領域が残ります。React NativeはWebの設計資産を流用し、デザイン/状態/テストの知識を横展開できるため、組織全体の教育コストを抑えやすい一方で、OS大規模アップデートやネイティブ依存の深い箇所では移行作業がボトルネックになり得ます。

粗い試算でも良いので、チームのスキル構成と要件の厳しさから、初期開発と一年間の保守を分けて見積もると意思決定が明瞭になります。例えばReact/TypeScriptシニア3名を中核に据え半年で初版を出す構成はReact Nativeでの再現性が高く、アクセラレートされたUIと高度なモーションを差別化軸に据える場合はFlutterでの少人数高密度開発が工期と品質の均衡点になりやすい。同じ「1コードベース」でも、実際の総コストは「人とプロセス」で大きく変動するという前提を、見積り表に反映させることが重要です。

まとめ:2025年、正しく迷い、正しく決める

どちらを選んでも勝てる時代だからこそ、迷い方に戦略が要ります。要件が曖昧なうちは、プロトタイプを2週間で並走させ、起動時間、初回描画、UI再現性、開発速度、バグ修正の再現性を同じ観測手順で比較してください。Flutterは「UIの純度で押し切る」戦略、React Nativeは「組織のスケールで押し切る」戦略に強いという解像度で、ロードマップと採用市場、運用戦略を束ねると腹落ちします。あなたのチームが次の四半期に勝ちたい戦場はどこか。その戦場で勝つために、どちらのフレームワークが「制約の少ない未来」を描いてくれるのか。小さく試し、計測し、納得してから賭けましょう。次のスプリントバックログに、二本のプロトタイプと四つの計測を、いま書き足すところから始めてください。

参考文献

  1. GitHub - flutter/flutter (Stars)
  2. GitHub - facebook/react-native (Stars)
  3. Flutter team. Racing forward at I/O 2023 with Flutter and Dart
  4. React Native team. The New Architecture is here
  5. React Native team. Hermes as the default
  6. Expo docs. EAS Update (Over-the-air updates)
  7. flutter.dev. Build for mobile, web, and desktop from a single codebase
  8. Expo docs. Web support (React Native for Web)
  9. Flutter docs. Architectural overview (Rendering with Skia)
  10. Dart docs. Native (AOT compilation overview)
  11. React Native docs. Architecture overview
  12. Flutter docs. Platform channels
  13. React Native docs. Native Modules Intro
  14. Flutter API docs. SchedulerBinding.addTimingsCallback
  15. React Native docs. InteractionManager
  16. Flutter GitHub Wiki. Roadmap
  17. React Native docs. New Architecture: Introduction and Migration
  18. Stack Overflow Developer Survey 2024. Web frameworks and technologies (popularity of React)