AI

クロスプラットフォーム開発比較:Flutter vs React Native vs Tauri

オープンソースラボ編集部2026年6月14日

クロスプラットフォーム開発比較:Flutter vs React Native vs Tauri でネイティブアプリを作る

Xamarin(Microsoft廃止予定)・Ionic(Cordova/Capacitor)に対して、Flutter(Google製・Dart・高パフォーマンス)・React Native(Meta製・TypeScript・Webエコシステム統合)・Tauri(Rust製・デスクトップアプリ特化・軽量)はクロスプラットフォームアプリをOSSで開発できます。

クロスプラットフォーム開発を選ぶ理由

  • 開発コスト1/2: iOS・Android・Web・デスクトップを1コードベースで開発
  • ネイティブパフォーマンス: Flutter(Skia/Impeller描画エンジン)・React Native(JSI Bridgeless)で60fps以上
  • 巨大エコシステム: Flutter 30k+・React Native 17k+のパブリックパッケージ
  • 企業採用実績: Flutter(Google Pay・Alibaba・eBay)・React Native(Facebook・Shopify・Discord)

主要ツールの概要

Flutter

2018年公開、Dart製のOSSです。GitHubスター165k+(最大)。単一コードベースでiOS・Android・Web・macOS・Windows・Linuxをターゲットにできるのが強みです。独自のSkia/Impeller描画エンジンにより、OSのウィジェットシステムに依存せず一貫したUIを実現します。

# Flutter SDK セットアップ(macOS)
curl -O https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_arm64_3.22.2-stable.zip
unzip flutter_macos_arm64_3.22.2-stable.zip
export PATH="$PWD/flutter/bin:$PATH"

# Flutter環境チェック
flutter doctor

# 新規プロジェクト作成
flutter create my_app --org com.yourcompany --platforms ios,android,web
cd my_app

# 開発サーバー起動(Chrome/iOS Simulator/Android Emulator)
flutter run -d chrome         # Web
flutter run -d "iPhone 15"   # iOS Simulator
flutter run -d emulator-5554 # Android Emulator
// Flutter: Riverpodを使ったAPIデータ取得サンプル
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:dio/dio.dart';

// APIデータのプロバイダー
final articlesProvider = FutureProvider.autoDispose<List<Article>>((ref) async {
  final dio = Dio();
  final response = await dio.get('https://api.yourapp.com/articles');
  return (response.data['data'] as List)
      .map((json) => Article.fromJson(json as Map<String, dynamic>))
      .toList();
});

// モデルクラス
class Article {
  final String id, title, summary, emoji;
  const Article({required this.id, required this.title, required this.summary, required this.emoji});
  factory Article.fromJson(Map<String, dynamic> json) => Article(
    id: json['id'] as String,
    title: json['title'] as String,
    summary: json['summary'] as String,
    emoji: json['emoji'] as String? ?? '📄',
  );
}

// メインウィジェット
class ArticleListPage extends ConsumerWidget {
  const ArticleListPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final articlesAsync = ref.watch(articlesProvider);
    return Scaffold(
      appBar: AppBar(title: const Text('OSS Lab Articles')),
      body: articlesAsync.when(
        data: (articles) => ListView.builder(
          itemCount: articles.length,
          itemBuilder: (context, index) {
            final article = articles[index];
            return ListTile(
              leading: Text(article.emoji, style: const TextStyle(fontSize: 28)),
              title: Text(article.title, style: const TextStyle(fontWeight: FontWeight.bold)),
              subtitle: Text(article.summary, maxLines: 2, overflow: TextOverflow.ellipsis),
              onTap: () => Navigator.pushNamed(context, '/article', arguments: article),
            );
          },
        ),
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (err, _) => Center(child: Text('Error: $err')),
      ),
    );
  }
}

void main() => runApp(const ProviderScope(child: MyApp()));

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'OSS Lab',
      theme: ThemeData(colorSchemeSeed: Colors.blue, useMaterial3: true),
      home: const ArticleListPage(),
    );
  }
}
# GitHub Actions: Flutter マルチプラットフォームビルド
name: Flutter Build
on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    strategy:
      matrix:
        platform: [android, ios, web]
        include:
          - platform: android
            os: ubuntu-latest
          - platform: ios
            os: macos-latest
          - platform: web
            os: ubuntu-latest

    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v4
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.22.2'
          channel: 'stable'

      - run: flutter pub get
      - run: flutter analyze

      - name: Build Android APK
        if: matrix.platform == 'android'
        run: flutter build apk --release
      - name: Build iOS IPA
        if: matrix.platform == 'ios'
        run: flutter build ipa --no-codesign
      - name: Build Web
        if: matrix.platform == 'web'
        run: flutter build web --release

      - uses: actions/upload-artifact@v4
        with:
          name: flutter-${{ matrix.platform }}
          path: |
            build/app/outputs/flutter-apk/
            build/ios/ipa/
            build/web/

React Native

2015年Meta公開、TypeScript/JavaScript製のOSSです。GitHubスター118k+。Reactの知識をそのままモバイル開発に活かせるのが最大の強みです。2024年の新アーキテクチャ(Bridgeless・JSI・Fabric)でネイティブコンポーネントとのやりとりが大幅に高速化されています。

// React Native + Expo: カメラ+AI画像解析サンプル
// app/(tabs)/camera.tsx
import { CameraView, useCameraPermissions } from 'expo-camera'
import { useState, useRef } from 'react'
import { View, Text, TouchableOpacity, StyleSheet, Alert } from 'react-native'

export default function CameraScreen() {
  const [permission, requestPermission] = useCameraPermissions()
  const [analyzing, setAnalyzing] = useState(false)
  const cameraRef = useRef<CameraView>(null)

  if (!permission?.granted) {
    return (
      <View style={styles.container}>
        <Text>カメラのアクセス許可が必要です</Text>
        <TouchableOpacity onPress={requestPermission} style={styles.button}>
          <Text style={styles.buttonText}>許可する</Text>
        </TouchableOpacity>
      </View>
    )
  }

  const analyzeImage = async () => {
    if (!cameraRef.current || analyzing) return
    setAnalyzing(true)
    try {
      const photo = await cameraRef.current.takePictureAsync({
        base64: true, quality: 0.8,
      })
      // Claude APIで画像解析
      const resp = await fetch('/api/analyze-image', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ image: photo?.base64 }),
      })
      const { analysis } = await resp.json()
      Alert.alert('AI解析結果', analysis)
    } finally {
      setAnalyzing(false)
    }
  }

  return (
    <View style={styles.container}>
      <CameraView ref={cameraRef} style={styles.camera} facing="back">
        <View style={styles.buttonContainer}>
          <TouchableOpacity
            style={[styles.button, analyzing && styles.disabled]}
            onPress={analyzeImage}
            disabled={analyzing}
          >
            <Text style={styles.buttonText}>
              {analyzing ? '解析中...' : 'AI解析'}
            </Text>
          </TouchableOpacity>
        </View>
      </CameraView>
    </View>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center' },
  camera: { flex: 1 },
  buttonContainer: { position: 'absolute', bottom: 40, width: '100%', alignItems: 'center' },
  button: { backgroundColor: '#3B82F6', paddingHorizontal: 32, paddingVertical: 16, borderRadius: 50 },
  disabled: { opacity: 0.5 },
  buttonText: { color: 'white', fontWeight: 'bold', fontSize: 16 },
})

Tauri

2021年公開、Rust製のOSSです。GitHubスター82k+。WebフロントエンドをRustバックエンドでラップしてデスクトップアプリを構築します。Electronよりバンドルサイズが1/10以下・メモリ使用量が1/3以下で、セキュリティポリシーが厳格です。

# Tauri 2.0 プロジェクトを作成
npm create tauri-app@latest
# フレームワーク選択: Next.js / SvelteKit / Vanilla

cd my-tauri-app
npm install
npm run tauri dev  # 開発モード

# ビルド
npm run tauri build
# 成果物: .msi (Windows) / .dmg (macOS) / .deb .rpm .AppImage (Linux)
// Tauri バックエンド(Rust)でシステム情報を取得
// src-tauri/src/main.rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use serde::{Deserialize, Serialize};
use sysinfo::{System, Disks};

#[derive(Serialize)]
struct SystemInfo {
    cpu_usage: f32,
    memory_used: u64,
    memory_total: u64,
    disk_used: u64,
    disk_total: u64,
}

#[tauri::command]
fn get_system_info() -> SystemInfo {
    let mut sys = System::new_all();
    sys.refresh_all();
    let disks = Disks::new_with_refreshed_list();
    let disk = disks.iter().next();
    SystemInfo {
        cpu_usage: sys.global_cpu_usage(),
        memory_used: sys.used_memory(),
        memory_total: sys.total_memory(),
        disk_used: disk.map(|d| d.total_space() - d.available_space()).unwrap_or(0),
        disk_total: disk.map(|d| d.total_space()).unwrap_or(0),
    }
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![get_system_info])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
// Tauri フロントエンド(TypeScript)からRustコマンドを呼び出し
import { invoke } from '@tauri-apps/api/core'
import { useEffect, useState } from 'react'

interface SystemInfo {
  cpu_usage: number
  memory_used: number
  memory_total: number
  disk_used: number
  disk_total: number
}

export function SystemMonitor() {
  const [info, setInfo] = useState<SystemInfo | null>(null)
  useEffect(() => {
    const interval = setInterval(async () => {
      const data = await invoke<SystemInfo>('get_system_info')
      setInfo(data)
    }, 1000)
    return () => clearInterval(interval)
  }, [])

  const fmt = (bytes: number) => `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`
  return info ? (
    <div className="p-4 font-mono text-sm space-y-2">
      <p>CPU: {info.cpu_usage.toFixed(1)}%</p>
      <p>RAM: {fmt(info.memory_used)} / {fmt(info.memory_total)}</p>
      <p>Disk: {fmt(info.disk_used)} / {fmt(info.disk_total)}</p>
    </div>
  ) : <p>Loading...</p>
}

機能比較表

比較項目FlutterReact NativeTauri
iOS/Android
デスクトップ△(Electron推奨)✅(Rust)
Web✅(Expo)
言語DartTypeScriptRust + Web
バンドルサイズ小(~5MB)
GitHub Stars165k+118k+82k+

クロスプラットフォーム開発はLow-codeカテゴリ/categories/low-codeのSupabase・Firebase代替バックエンドと組み合わせてリアルタイムデータ同期付きモバイルアプリを構築します。DevOpsカテゴリ/categories/devopsのGitHub ActionsでFlutterのマルチプラットフォームビルドを自動化してTestFlight/Google Play Consoleへの配信を行います。

FAQ

Q. FlutterとReact Nativeの実装コストはどちらが低いですか?

A. Webエンジニア(TypeScript経験あり)にはReact Nativeのほうが最初の生産性が高いです。Flutterは新言語Dartの学習が必要ですが、2週間程度で実用レベルに達します。コスト比較: ①Web兼任の場合: React Native + ExpoはReact/TypeScriptの知識をそのまま活用でき、Next.jsエンジニアが最速でモバイル対応できる②バックエンド兼任の場合: Flutterは型安全なDartが使いやすく、Androidネイティブ開発者にも親和性が高い③デザイナー兼任の場合: FlutterはウィジェットツリーでFigmaのコンポーネント設計に近いUI実装ができる。2024年時点でFlutter・React Nativeの日本語求人数はほぼ同程度で、どちらも就職市場での需要は高い状況です。

Q. TauriはElectronと比べてどれだけ軽いのですか?

A. 実際のアプリで比較: VS Code(Electron)約150MB vs Tauri製同等アプリ約5〜20MB。具体的な差: ①バンドルサイズ: Electron(Chromium内蔵)は最小60MB〜、Tauri(OSのWebview使用)は2〜5MB②メモリ使用量: Electronは起動時100MB〜、Tauriは30〜50MB③起動速度: Tauriのほうが明らかに高速。制限: Tauriはブラウザ(WebKit/WebView2)をOSから借りるため、ブラウザエンジンの差(macOS=WebKit、Windows=WebView2)でUIの見た目が少し異なるケースがある。Electronのほうが向くケース: Chromium固定でブラウザ差異をゼロにしたい場合・Nodeモジュールをネイティブコードとして直接実行したい場合。

Q. Flutter WebはSEOに弱いのですか?

A. Flutter Webは現時点でSEOに課題があります(2024年時点)。理由: ①デフォルトのCanvasKit(WebGL描画)はDOMを生成しないためクローラーがコンテンツを読み取りにくい②HTML renderer(Flutterの設定: --web-renderer html)を使えばDOMが生成されるが、SemanticTree(アクセシビリティ用の隠し要素)が複雑になる③ページ間のナビゲーションはSPAのためプリレンダリングが必要。対策: ①コンテンツページ(ブログ・LP)はNext.jsで構築してFlutter Webを管理画面・インタラクティブ機能に限定②Google Search ConsoleでFlutter Webのインデックス状況を確認してCoverage Reportをモニタリング③flutter build web --web-renderer htmlでビルドして構造化データをmetaタグで補完。

Q. React NativeはNew Architecture(Bridgeless)に移行すべきですか?

A. React Native 0.74+(2024年)では New Architecture がデフォルトで有効になっています。移行メリット: ①Bridgeless mode(JS-Nativeの直接通信でJSThread→Bridgeのオーバーヘッド排除)でアニメーション・タッチイベントが大幅改善②Concurrent Mode(React 18の同時レンダリング)がRNでも使用可能③Turbo Modules(ネイティブモジュールのレイジーロード)でアプリ起動速度15〜30%短縮。確認事項: 使用ライブラリがNew Architectureに対応しているか(react-native-camera等の古いライブラリは要確認。reactnative.directoryでNew Arch対応状況を確認できます)。既存プロジェクト: react-native upgrade後にnewArchEnabled=true(Android: gradle.properties、iOS: Podfile)を設定して段階的に移行可能。

まとめ

ユースケース推奨ツール
iOS/Android + Web + デスクトップ統合Flutter
Reactエコシステム・WebエンジニアチームReact Native
軽量デスクトップアプリ・Rust統合Tauri

関連外部リソース

他の記事も読む

Let's Build Together

OSS導入、自社だけで悩まない。

ツール選定から構築・運用・AI活用まで、オープンソースラボ運営元のClasslessが伴走します。初回のご相談は無料です。