expo-web-to-native について
expo-web-to-native は、既存の React Web アプリを段階的にネイティブへ移行し、画面の変換中もリリース可能な状態を保つための Expo スキルです。評価、DOM ベースのシェル、画面の再設計、データ接続、リリースまでを案内します。
主な機能
- 01画面の評価と分類
- 02DOM コンポーネントのシェルから開始
- 03画面を段階的にネイティブ化
- 04参考資料に基づくネイティブ再設計
- 05実行中の画面を Web アプリと照合
- 06データ、認証、ストレージの接続
活用シーン
- 01既存 React アプリの段階的な移行計画
- 02ネイティブ画面の開発前に Web シェルを公開
- 03ネイティブ再設計する画面の優先順位付け
- 04ネイティブ画面と Web 版の動作を比較
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ expo/ skills --skill expo-web-to-nativeインストール後、Claude Code で/expo-web-to-nativeを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlreferences/false-friends.mdnative-patterns.mdrun-as-goal.mdverify-on-device.md詳細
Web からネイティブへ
Web の React アプリはネイティブに変換されるのではありません。トランスパイラーはありません。イチジクが木を囲むように育ち、ゆっくり置き換えていくのと同じように、画面ごとに移行します。まずネイティブのシェルを立ち上げ、初日から Web UI 全体をその中で動かし、その後、優先順位に沿って画面を一つずつネイティブ化していきます。このスキルは作業の順序を定める軸となり、各ステップでは説明を繰り返すのではなく、既存の Expo スキルに引き継ぎます。これは Expo のReact を使った Web からネイティブへの移行を実践するものです。理由については、そちらをお読みください。
flowchart TD
A1[1 · Assess: write the worklist] --> A2[2 · Scaffold Expo shell]
A2 --> A3[3 · DOM-component shell<br/>· expo-dom · SHIP DAY ONE]
A3 --> A4[4 · Strangle screens to native<br/>highest-value first · expo-router]
A4 -->|more screens| A4
A4 --> A5[5 · Wire data / auth / storage<br/>· expo-data-fetching]
A5 --> A6[6 · Ship · eas-app-stores]
原則
- 書き直すのではなく、移行します。 一度にすべてを置き換えてはいけません。どのステップでもアプリをリリース可能な状態に保ちます。
- 初日からリリースします。 何かをネイティブ化する前に、DOM コンポーネントのシェル(ステップ 3)で Web UI を動かします。これがマイルストーンであり、その後はすべて改善です。
- 価値に基づいて段階的に置き換えます。 利用頻度の高い画面をネイティブ化し、それ以外は WebView に残します。DOM の画面はそれぞれ約 2 MB の Web ランタイムを必要とします。すべてを DOM としてリリースしない理由はこれで十分です。
- ネイティブ化とは、見た目を変えることではなく、再設計することです。 段階的に置き換えた画面は、Web ページの見た目を変えただけではなく、Apple や Google がリリースしたかのように仕上げるべきです。まず
@expo/uiを使ってください。実際の SwiftUI/Compose をレンダリングするため、まさに OS の一部のように感じられます。スタイルを適用した RN プリミティブは、カスタムレイアウトに限って代替手段とします。さらに、プラットフォームのナビゲーション(expo-routerの NativeTabs や大きなタイトル)、@expo/uiによるリキッドグラスやネイティブコンポーネント、シート、スワイプ、ハプティクスといったモバイル UX も活用します。Web からネイティブへのパターン対応表は、./references/native-patterns.mdにあります。それでも Web サイトのように感じられるなら、再設計ではなく移植をしたということです。 - コンパイルではなく、実行して検証します。 ビルドが成功しても何の証明にもなりません(空の WebView でも問題なくコンパイルできます)。各画面を実行して検証します。ただし、Web の元画面と比較するのはコンテンツと動作であり、ピクセル単位の見た目ではありません(ネイティブ化した画面は、元画面と同じではなく、よりネイティブらしく見えるべきです)。
- 作り直すのではなく、適切に進めます。 各ステップでは既存のスキルを利用します。ここで重要なのは順序と注意点です。イディオムごとの対応については、
./references/false-friends.mdを参照してください。
ループとして実行する(推奨)
移行は完了するまで繰り返す長いループです。そのため、最初にすべきことは、画面を手作業でひたすら進めるのではなく、目標を記述して実行を開始することです。このアプリ用に./references/run-as-goal.mdの目標を記入して提示してください。各反復でこのスキルを読み直すため、/goal を実行するたびにプレイブックと作業リストが再読み込みされ、次の画面に進みます(評価ステップも自動で初期化されます)。それを指定して /goal を実行してください。ハーネスでループできない場合は、migration-goal.md に記述し、ユーザーに実行を開始してもらいます。以下のステップは各反復で行う内容です。ループしない場合に限り、手作業で実行してください。
移行
移行対象のリポジトリがない場合 — Web 開発者として、最初からネイティブアプリを構築するだけですか? その場合、これらのステップは不要です。
expo-routerを使い、Web からネイティブへのイディオム対応表である./references/false-friends.mdを開いておいてください。以下はすべて、既存の Web アプリがあることを前提としています。
1. 評価する → ワークリストを作成する
リポジトリを読み、migration-progress.md を作成します。これは移行の残りの作業でチェックを付けていく、耐久性のあるワークリストです。2 つの切り口で分けます:
- 画面 vs バックエンド。 ページルート(
page.tsx)は移行する画面です。サーバールート(route.ts)、ORM、認証ハンドラーはサーバー側に残します。バックエンドは一度だけ決めます: デプロイしたままにする(ネイティブアプリが HTTP クライアントになる)か、EAS Hosting(eas-hosting)へ移すかです。 - 各画面をバケット分けする。 どのように着地させるかで分類します: port-as-is(プレゼンテーショナル → DOM webview で配信)、nativize-now(ホット、またはネイティブな操作感が必要 — ジェスチャー、リスト、キーボード)、nativize-later、または hybrid(web サブツリーを囲むネイティブシェル。例: markdown レンダラーを包むチャットリスト)。
読みながらフレームワークのシグナルに注目してください — RSC かクライアントか、Tailwind/shadcn、どこでデータを取得しているか — これらが各画面をどう移植するかを決めるからです(false-friends に対応表があります。特に非同期 Server Components は、移動できるようになる前にクライアントフェッチ + プレゼンテーショナルコンポーネントに分割する必要があります)。サードパーティのサービス/SDK にもフラグを立ててください — ブラウザ SDK はそのまま持ち越せません(false-friends → *Services & SDKs`)。特に決済は 置き換えではなく分岐 です(アプリ内デジタル商品は RevenueCat 経由のストア IAP を使う必要があり、約 30% — Stripe ではありません)。これは今決めるべきビジネスモデル上の判断であり、App Store レビューの時点で決めるものではありません。ワークリストは、すべてのルートが整理され、すべての画面がバケット分けされて初めて信頼できるものになります。
2. シェルをスキャフォールドする
create-expo-app を実行し、Expo Router で web のルートをミラーします — Next のツリーはほぼ 1:1 で対応します([id]/page.tsx → [id].tsx に注意。またルートは src/app/ に置かれる場合があります)。各ルートに 1 つずつ、空の画面を用意します。
3. DOM コンポーネントでシェルに収める — 初日のマイルストーン
すべての画面を DOM コンポーネント('use dom'、expo-dom スキルのとおり)として持ち込み、それぞれのネイティブルートでレンダリングします。これにより、何かをネイティブ化する前にアプリ全体がスマートフォン上で動作します。画面ごとの編集が発生すると想定してください — Server Components のラップ解除、フレームワークのインポート(next/link)の差し替え、スタイリングの持ち込み — いずれも false-friends で扱っています。その後、(下記の)実行で検証します。これはそのまま TestFlight に出荷できます。
4. 画面をネイティブへ締め上げる — 価値の高い順に
migration-progress.md を上から順に進めます。各画面について、ネイティブとして再設計 します — web のレイアウトを移植しないでください。まず @expo/ui に手を伸ばします(本物の SwiftUI/Compose — ボタン、リスト、シート、ピッカー、スライダー。./references/native-patterns.md に、どの web パターンがどのネイティブコンポーネントになるかの対応表があります)。次にプラットフォームのナビゲーション(expo-router — NativeTabs、ラージタイトル)とモバイル UX(スワイプ、ハプティクス、モメンタム/反転スクロール)を検討します。RN プリミティブはカスタムレイアウトにのみ使います。各イディオムについては ./references/false-friends.md を参照してください。@expo/ui と DOM コンポーネントはどちらも Expo Go(SDK 56+)で動作します — 開発ビルド(expo-dev-client スキル)が必要になるのは カスタム ネイティブモジュールの場合だけです。コンテンツと挙動 を、動作中の web 版と突き合わせて検証し(見た目はよりネイティブになっているはずです)、チェックを付けます。1 パスにつき 1 画面、その間ずっとアプリは出荷可能です。耐久性のあるワークリストを回すループなので、無人で実行できます — ゴールループ(./references/run-as-goal.md)に渡してください。
5. データ、認証、ストレージを接続する
web のデータ層は移行を生き延びません — 相対フェッチ、Cookie セッション、localStorage、env 変数はすべて変わります(false-friends に差し替えがあります)。リクエストとキャッシュには expo-data-fetching を使います。バックエンドを EAS Hosting に移した場合は eas-hosting を追加します。
6. 出荷する
ストアビルド(App Store / Play / TestFlight)には eas-app-stores、その後の OTA プッシュには EAS Update を使います。
コンパイルではなく実行して検証する
expo export が成功しても証明できるのは画面がバンドルされることだけで、正しくレンダリングされることではありません。画面はビルドできても、真っ白になったり、誤ってレンダリングされたりすることがあります。シェルの後と、ネイティブ化した各画面の後に、同じルートで実行中の両方のアプリを比較してください。
- Web のオリジナル —
agent-browser(vercel-labs CLI)でキャプチャします。ルートをopenし、アクセシビリティツリーをsnapshot --jsonで取得して、screenshotを撮ります。 - ネイティブ — **
argent**でシミュレーターを操作します。構造の確認にはdescribe/debugger-component-treeを使い、各パスでチェックを再実行するにはflowを使います。
ピクセルではなく、コンテンツと動作の同等性で合格とします。ネイティブ化した画面はWebよりもネイティブらしく見えるべきで、同一にしてはいけません(DOMシェルの段階は例外です。この段階ではWeb UIそのものなので、一致するべきです)。使用感もネイティブ体験の一部であり、スクリーンショットでは捉えられません。トランジションやジェスチャーのある画面では、静止画だけでなく短い録画を撮ってください(native-patterns.md → 使用感を参照)。この反復作業では使用するツールを指定しています。agent-browserまたはargentがインストールされていない場合は、ユーザーに確認してから作業を進める前にインストールしてください。手動のスクリーンショットで代用してはいけません。手順とセットアップの詳細は./references/verify-on-device.mdを参照してください。
参考資料
./references/false-friends.md— Web の慣用表現からネイティブでの相当表現への置き換えと、それぞれの注意点です。手順3~5、およびWeb開発者が慣用表現を捨てる際に参照します。./references/native-patterns.md— Web の UX パターンからネイティブ向けの再設計への置き換えです(@expo/uiを優先)。画面を単に見た目だけ変えるのではなく、OS ネイティブの使い心地にするための、手順4の再設計プレイブックです。./references/verify-on-device.md— 2つのエージェントを使った同等性確認の手順です。Web アプリ(ブラウザーエージェント)とネイティブアプリ(argent)を操作して、同じルートを開いて比較します。./references/run-as-goal.md— 手順4を無人で実行するための、移行に特化した目標の目的文です(反復ごとにこのスキルを再読み込みします)。- Expo — React で Web からネイティブへ — このスキルが実践する標準ガイドです。
フィードバックの送信
このスキルにエラー、誤解を招く情報、または古い情報があれば、Expo が改善できるよう報告してください。
npx --yes @expo/agent-cli@latest feedback --category skills --subject "expo-web-to-native" "<actionable feedback>"
具体的で実行可能な報告事項がある場合にのみ送信してください。関連する背景情報をできるだけ詳しく記載してください。
エージェントが Expo のタスクで繰り返し失敗する場合や、ユーザーが介入しなければならない場合は、タスク、期待する結果、問題の内容を簡潔に報告し、--category evalsと、関連する Expo のパッケージまたはコマンドを--subjectとして指定してください。