クロスプラットフォーム開発比較: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>
}
機能比較表
| 比較項目 | Flutter | React Native | Tauri |
|---|---|---|---|
| iOS/Android | ✅ | ✅ | ❌ |
| デスクトップ | ✅ | △(Electron推奨) | ✅(Rust) |
| Web | ✅ | ✅(Expo) | ✅ |
| 言語 | Dart | TypeScript | Rust + Web |
| バンドルサイズ | 中 | 中 | 小(~5MB) |
| GitHub Stars | 165k+ | 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 |