はじめに
こんにちは。クロスプラットフォーム開発の選定に頭を悩ませているエンジニアです。 「一度書けばどこでも動く(Write Once, Run Anywhere)」——これは我々エンジニアにとって永遠の夢ですが、現実はそれほど甘くありません。
今回、「Webアプリの資産を活かしたネイティブアプリ開発」を目指してReact Nativeに取り組みましたが、特にデスクトップ(Windows)対応において想像以上の「茨の道」を経験しました。その結果、Flutterへの移行を決断するに至った経緯と、その技術的な裏付けを共有したいと思います。
クロスプラットフォーム・フレームワークの概況
まず、現在利用可能な主要なクロスプラットフォーム技術を整理します。モバイルだけでなくデスクトップ(Windows/macOS)も視野に入れると、選択肢は以下のように絞られます。
| フレームワーク | 言語 | 主幹企業 | 特徴 | 今回の評価 |
|---|---|---|---|---|
| React Native | JS / TS | Meta / Microsoft | Web技術(React)を使用。OS標準のUIを描画。 | Web資産流用は魅力的だが、デスクトップ対応に難あり。 |
| Flutter | Dart | 独自の描画エンジン(Skia/Impeller)を使用。全OSで均一なUI。 | 今回の採用候補。安定性とパフォーマンスが高い。 | |
| .NET MAUI | C# | Microsoft | Xamarinの後継。Windowsとの親和性は最強だが、モバイル開発体験は好みが分かれる。 | Windows特化ならありだが、モバイル含めると学習コスト高。 |
| Electron | JS / TS | OpenJS Fdn. | Web技術そのもの(Chromium)で動作。 | デスクトップ専用のため、モバイル対応不可。 |
| Unity | C# | Unity | ゲームエンジン。 | ゲーム専用。一般的なUIアプリには不向き。 |
当初、Web(React)の知見を最大限活かせる React Native が最適解だと考え、プロジェクトをスタートしました。
(余談)Expo vs React Native CLI の落とし穴
開始当初、私は「Expoは開発用で、最終的に純粋なネイティブコードにはならない」という古い認識を持っていました。そのため、あえて難易度の高い React Native CLI を選択しました。
しかし、調査を進めると現在の Expo は prebuild 機能により、完全にネイティブなプロジェクトを生成できることが判明しました。CLIを選んでしまったことで、Windowsのビルド環境(Visual Studioのソリューションファイル)を自力で管理する羽目になり、これが後の苦戦の伏線となりました。
React Native での挑戦と「構造的限界」
React Nativeでの開発を始めてすぐに、「Webのように書けるが、Webではない」 という現実に直面しました。特にWindows対応において、以下の構造的な壁にぶつかりました。
エコシステムの分断
React Nativeは「一枚岩」ではありません。
- React: Webの本家ライブラリ(npmでインストール)。
- React Native (Core): モバイル(iOS/Android)向け。主幹は Meta。
- React Native for Windows / macOS: デスクトップ向け。主幹は Microsoft。
- ここがポイント: コア(Meta)とは別リポジトリで開発されており、コアの最新版への追従が遅れがちです。
- React Native CLI: 開発ツール。主幹は コミュニティ(Callstack社など)
- Expo: 開発ツールチェーン。主幹は 650 Industries (Expo)。
この開発主体の違いにより、Coreの最新機能にWindows版が追従できていない「周回遅れ」の状態が頻発します。
Windows対応の辛すぎる現実
特に苦戦したのがWindows版です。「React Native for Windows」導入時に以下の問題が多発しました。
- UWPの制約と署名のコスト: Windows版は歴史的にUWP(サンドボックス環境)ベースです。このため、プロトタイプを社内に配ろうとしても、UWPはデジタル署名が必須となります。有償の証明書(年間数万円〜)を購入しないとインストールすらままならない現実は、プロトタイプ開発において大きな足枷でした。また、UWPゆえにファイルアクセス権限やWin32 APIへのアクセスが非常に制限されていて、回避策の検討にも苦労します。
- ライブラリ品質のばらつき:
react-native-*系のライブラリは、iOS/Androidには対応していても、Windowsは未対応かビルド不能であることが多々あります。 - Hermesエンジンのクラッシュ: Windows版のJSエンジン「Hermes」が、国際化機能(Intl)を呼ぶだけでクラッシュするバグに遭遇しました。プラットフォーム固有の地雷を踏むたびに開発がストップしました。
- アーキテクチャの過渡期: React Nativeは現在、旧アーキテクチャ(Bridge)から新アーキテクチャ(Fabric / TurboModules)への移行期です。しかし、Windows版はこの対応が完了しておらず、ライブラリによっては「モバイルは新アーキテクチャ必須だが、Windowsは未対応」という板挟み状態になります。
結果として、「Reactのコードは共通化できても、ネイティブ周りのトラブルシューティングで工数が倍増する」 という本末転倒な状態に陥りました。
なぜ Flutter なのか? 技術的な優位性
「純粋なクロスプラットフォームアプリ」を作るという目的に立ち返ったとき、Flutter のアーキテクチャが持つ合理性が際立ちます。
「インタープリター」vs「ネイティブコード」
性能面で決定的な違いがあります。
- React Native: リリースビルドであっても、JavaScriptコードはバイトコード化され、アプリ内のJSエンジン(VM)上で実行されます。つまり、ネイティブコードとの間には常に変換のオーバーヘッドが存在します。
- Flutter: リリースモードでは AOT (Ahead-Of-Time) コンパイル され、そのプラットフォームの完全なネイティブ機械語(マシンコード) になります。C++で書いたアプリと同等にCPUが直接実行するため、起動速度も処理速度も圧倒的です。
配布の容易さ(.exe vs MSIX)
FlutterのWindows版は、標準的なWin32アプリ(.exe)としてビルドされます。署名なしでも(警告は出ますが)zipで固めて配布し、ダブルクリックで即起動できます。この「普通のWindowsアプリ」が作れる手軽さは、React Native (UWP/MSIX) との大きな差です。
FFI による高速な連携と安定性
React Nativeが複雑なブリッジを介するのに対し、Flutter (Dart) は FFI を使って C/C++ (Win32 API) を直接呼び出せます。OS機能へのアクセスが高速で、かつGoogleが公式プラグインを整備しているため、「Windowsだけ動かない」という事態が起きにくい構造になっています。
Googleによる中央集権的な品質管理
Flutterは言語(Dart)もフレームワーク本体も Google が主幹です。 さらに、ファイルシステム、HTTP通信、画像選択といった主要なプラグインもGoogle(またはFlutterチーム)が公式にメンテナンスしています。 「Windowsだけ動かない」という事態が構造的に起きにくく、Windows版もStable(安定版)として本体に含まれています。
AI時代における「言語の壁」の崩壊
移行に際して唯一の懸念は「Dart」という馴染みのない言語でした。しかし、実際に触れてみると2つの発見がありました。
- AIコーディングとの相性: Dartは静的型付け言語です。AIにコードを書かせた際、型定義が間違っていればコンパイルエラーになるため、JavaScriptよりも「AIの嘘」に気づきやすいです。
- 「書く」のはAI、「読む」のは人間: 新しい言語をゼロから「書く」のは大変です。しかし、AIに生成させたコードを「読んでレビューする」のは、JavaやTypeScriptに近いDartなら難しくありません。
「AIに書いてもらい、人間は型安全な環境でレビューする」 ——このスタイルが確立できる今、言語のマイナーさはもはや障壁ではなくなりました。
おわりに
Web資産の流用やOS標準UIへのこだわりがあるなら React Native も選択肢ですが、デスクトップを含む真のクロスプラットフォーム展開と、ネイティブアプリとしての純粋な性能・配布のしやすさを求めるなら、2025年現在は Flutter が最適解であると判断しました。
今後は今回の知見を活かして、Flutterでクロスプラットフォームのネイティブアプリを量産できるようにしていきたいと思います。





















