📱web-to-native
- プラグイン
- expo
- ライセンス
- MIT
- ソース
- GitHub で見る ↗
説明
既存のウェブReactアプリをExpoを使ってネイティブなiOS/Androidアプリに移行させるガイドです。 次のような場合に使用: - ウェブサイトをモバイルアプリに変更したい場合 - Next.js・Vite・CRAなどで構築したReactコードベースをReact Nativeに移植したい場合 - ウェブのコードをネイティブアプリ側で段階的に再利用したい場合 - ウェブの開発手法(DOM、CSS、React Router、localStorage、windowなど)がネイティブアプリでどのように対応するかを知りたい場合 このスキルは、ウェブからネイティブへの移行全体をカバーする総合ガイドです。DOMコンポーネント(HTML要素の仕組み)そのものについては、`use-dom`スキルをご使用ください。
原文を表示
Migrate an existing web React app to a native iOS/Android app with Expo. Use when the user wants to turn a website into a mobile app, port a Next.js/Vite/CRA React codebase to React Native, reuse web code on native incrementally, or asks how web idioms (the DOM, CSS, React Router, localStorage, window) map to native. This is the end-to-end migration guide; use the `use-dom` skill for the DOM-component mechanism itself.
ユースケース
- ✓ウェブReactアプリをiOS/Androidアプリに移行させたい
- ✓ウェブコードをネイティブアプリで段階的に再利用する
- ✓ウェブの開発手法がネイティブアプリでどう対応するか知りたい
本文(日本語訳)
ウェブからネイティブへ
ウェブのReactアプリは変換してネイティブアプリになるのではなく、トランスパイラ(異なるプログラミング言語や記述形式へ自動変換するツール)はありません。画面ごとに段階的に移行します。イチジクの木が他の木に絡みつき時間をかけて置き換わっていくように、ネイティブの外殻を構築し、初日は全ウェブUIをその中で実行してから、優先度順に各画面をネイティブに置き換えていきます。このスキルはその作業の順序を決めるものです。各ステップでは既存のExpoスキルに引き継ぎ、説明の繰り返しを避けます。Expoの「WebからReactでネイティブへ」ガイドを運用化しています — 背景については、そちらをお読みください。
flowchart TD
A1[1 · 現状把握: 作業リストを作成] --> A2[2 · Expoの外殻を構築]
A2 --> A3[3 · DOM・コンポーネント外殻<br/>· use-dom · 初日リリース]
A3 --> A4[4 · 画面をネイティブに置き換え<br/>高価値順 · building-native-ui]
A4 -->|さらに画面あり| A4
A4 --> A5[5 · データ / 認証 / ストレージを連携<br/>· native-data-fetching]
A5 --> A6[6 · リリース · expo-deployment]
原則
-
書き直さず段階的に移行する。 一度にすべて置き換えてはいけません。各ステップでアプリはいつでもリリース可能な状態を保ちます。
-
初日にリリースする。 ウェブUIは何もネイティブ化する前にDOM・コンポーネント外殻(ステップ3)で実行されます。これがマイルストーンです。その後はすべて磨き上げです。
-
価値で置き換える。 よく使われる画面をネイティブ化し、残りはウェブビュー(ウェブ上の表示領域)のままにしておきます。DOM画面は1つあたり約2 MBのウェブランタイムを含むため、すべてをDOMで出荷しない理由になります。
-
ネイティブ化は「デザイン改善」。見た目だけの変更ではありません。 置き換えた画面はAppleやGoogleが出荷したように見えるべき、ウェブページの見た目を整えたものではありません。まずは
@expo/uiを使いましょう — これは本物のSwiftUI/Composeをレンダリング(描画)するため、OSそのものように感じられます。スタイル付きのReactNativeの基本要素(primitives)は、カスタムレイアウトにしか使いません。加えてプラットフォーム固有のナビゲーション(building-native-ui:NativeTabs、大きなタイトル)、液体ガラス効果やネイティブコンポーネント(@expo/ui経由)、モバイルUX(シート、スワイプ、触覚フィードバック)も活用します。ウェブからネイティブへのパターン対応表は./references/native-patterns.mdです。ウェブサイトのように見えたら、移植はできていても再設計はできていません。 -
コンパイルではなく、実行で確認する。 クリーンなビルドが成功しても何も証明されません(空のウェブビューはコンパイルが通ります)。各画面を実行して確認してください。ただし判定はピクセル単位ではなく、内容と動作でウェブ版と比較します。ネイティブ化された画面はウェブ版より見た目がネイティブになるはずで、完全に同じになることはありません。
-
仕組みを活かす。再発明しない。 各ステップは既存スキルに振り分けられます。このスキルの価値は順序とよくある落とし穴です — ウェブとネイティブの慣例対応表は
./references/false-friends.mdにあります。
ループとして実行する(推奨)
移行は長く繰り返すループなので、最初のステップは目標を決めて開始することです。画面を手動でコツコツ進めるのではなく。./references/run-as-goal.mdに目標を記入してこのアプリ用に提示してください。このスキルを毎回のループで再読み込みするので、/goalを実行するたびにプレイブック(手順)と作業リストが更新され、次の画面を進められます。自動的に現状把握ステップも起動します。その後/goalを実行してください。または、ループ機能が使えない場合はmigration-goal.mdに書いて、ユーザーに実行してもらいます。以下のステップは、各ループ反復で行うことです。ループしない場合のみ、手動で実行してください。
移行の流れ
移行するリポジトリがない場合 — ウェブ開発者としてゼロからネイティブ開発を始めたい?このステップは不要です:
building-native-uiを使い、./references/false-friends.mdをウェブからネイティブへの対応表として開いておきましょう。以下はすべて既存ウェブアプリを持っている想定です。
1. 現状把握 → 作業リストを作成
リポジトリを読んでmigration-progress.mdを作成します。これは移行全体で参照・チェックオフしていく、保存版の作業リストです。2つの観点で分類してください。
-
画面とバックエンド。 ページルート(
page.tsx)は移行対象の画面です。サーバールート(route.ts)、ORM、認証ハンドラはサーバー側に残します。バックエンドについては一度決めてください:デプロイしたまま保つ(ネイティブアプリはHTTPクライアントになる)か、EAS Hostingに移す(expo-api-routes)か。 -
各画面を分類します:そのままポート(見た目だけの画面 → DOMウェブビューで出荷)、すぐにネイティブ化(よく使う画面、またはネイティブ操作感が必要 — ジェスチャー、リスト、キーボード)、後でネイティブ化、またはハイブリッド(ウェブの部分ツリーをネイティブの外殻で囲む。例:チャットリストがマークダウンレンダラーをラップしている)。
読み込み中にフレームワークの特徴を記録してください — RSC(React Server Components)かクライアントコンポーネントか、TailwindやShadcn、データはどこで取得するか — これらが各画面のポート方法を決めます(false-friendsに対応表があります。特にasyncなServer Componentsはクライアント側のフェッチ+プレゼンテーション用コンポーネントに分割してから移行する必要があります)。サードパーティサービス/SDKもフラグを立てておきましょう — ブラウザ向けSDKは持ち越せません(false-friends → サービス&SDK参照)。特に決済は置き換えではなく分岐です(デジタル商品の代金決済はRevenueCat経由でストアIAP(アプリ内課金)を使う必要があり、約30%のコミッション — Stripeではなく)。これはApp Store審査の時点ではなく、今決めるべきビジネスモデルの判断です。作業リストはすべてのルートが分類され、すべての画面が分類されたときだけ信頼できるものになります。
2. 外殻を構築
create-expo-appを実行し、Expo Routerでウェブルートをミラー(複製)します — Nextのツリーはほぼ1対1でマップされます(ただし[id]/page.tsx → [id].tsxに。ルートはsrc/app/に入るかもしれません)。ルートごとに空の画面を1つ作ります。
3. DOM・コンポーネントで外殻を作る — 初日のマイルストーン
すべての画面をDOMコンポーネント('use dom'を使い、use-domスキル参照)として持ってきて、ネイティブルートでレンダリングします。これでウェブアプリ全体が何もネイティブ化する前にスマートフォンで動きます。画面ごとの編集を予想してください — Server Componentsのアンラップ、フレームワークのインポート置き換え(next/linkなど)、スタイルの移行 — すべてfalse-friendsで説明されています。その後、実行して確認してください(下記)。この状態でTestFlightに出荷できます。
4. 画面をネイティブに置き換える — 価値順で
migration-progress.mdを上から順に進みます。各画面について、ウェブレイアウトを移植するのではなく、ネイティブで再設計してください。@expo/uiを最優先に(本物のSwiftUI/Compose — ボタン、リスト、シート、ピッカー、スライダー。ウェブのパターンがどのネイティブコンポーネントになるかは./references/native-patterns.mdで対応表を確認)、次にプラットフォーム固有ナビゲーション(building-native-ui — NativeTabs、大きなタイトル)とモバイルUX(スワイプ、触覚フィードバック、モーメンタムスクロール)。ReactNativeの基本要素はカスタムレイアウト用のみ。各慣例について./references/false-friends.mdを確認してください。@expo/uiとDOMコンポーネントはどちらもExpo Go(SDK 56以上)で動きます — カスタムネイティブモジュール用のでビルド(expo-dev-clientスキル)だけが必要です。実行中のウェブ版と比べて内容と動作を確認(見た目がより一層ネイティブになるはず)してからチェックオフします。1パス1画面、アプリはいつでもシップ可能。作業リストが保存版なので、ループでチェックオフできます。目標ループに任せることもできます(./references/run-as-goal.md参照)。
5. データ、認証、ストレージを連携
ウェブのデータレイヤーは移行を生き残りません — 相対パスでのフェッチ、クッキーセッション、localStorage、環境変数はすべて変わります(false-friendsで置き換え方あり)。リクエストとキャッシング用にnative-data-fetchingを使い、バックエンドをEAS Hostingに移した場合はexpo-api-routesを追加します。
6. リリース
expo-deploymentでストアビルド(App Store / Play / TestFlight)を作り、その後OTA(ネット経由の更新)プッシュはEAS Updateで行います。
実行で確認、コンパイルではなく
expo exportが通っても、画面がレンダリングされることは証明されません — ビルドが成功しても、表示が空白か間違っていることはあります。外殻を作った後、ネイティブ化した画面ごとに、同じルートで2つのアプリを実行して比較してください:
-
ウェブ版 —
agent-browser(vercel-labs CLI)で記録します:ルートをopenし、アクセシビリティツリーをsnapshot --jsonで取得し、screenshotします。 -
ネイティブ版 — シミュレータを**
argent**で操作します:describe/debugger-component-treeで構造を確認、flowで毎回のチェックを再生します。
内容と動作で一致させてください — ピクセルではなく:ネイティブ化された画面はウェブよりより一層ネイティブに見えるはずで、完全に同じになることはありません(DOM外殻ステージは例外 — そこはウェブUIなので、一致するはず)。feel(操作感)はネイティブの一部で、スクリーンショットでは記録できません — トランジションやジェスチャーがある画面は、静止画ではなく短い動画を記録してください(native-patterns.md → Feel参照)。このループはツール選定について厳密です:agent-browserまたはargentがインストールされていない場合は、ユーザーに確認して進める前にインストールしてください — 手動スクリーンショットへのフォールバックはしません。完全な手順とセットアップは[./references/verify-on-device.md](./references/verify-
原文(English)を表示
Web to Native
A web React app does not convert to native — there is no transpiler. It migrates, screen by screen, the way a strangler fig grows around a tree and slowly replaces it: stand up a native shell, run the whole web UI inside it on day one, then strangle each screen into native in priority order. This skill is the spine that orders the work; each step hands off to an existing Expo skill rather than re-explaining it. It operationalizes Expo's From Web to Native with React — read that for the why.
flowchart TD
A1[1 · Assess: write the worklist] --> A2[2 · Scaffold Expo shell]
A2 --> A3[3 · DOM-component shell<br/>· use-dom · SHIP DAY ONE]
A3 --> A4[4 · Strangle screens to native<br/>highest-value first · building-native-ui]
A4 -->|more screens| A4
A4 --> A5[5 · Wire data / auth / storage<br/>· native-data-fetching]
A5 --> A6[6 · Ship · expo-deployment]
Principles
- Migrate, don't rewrite. Never big-bang it; every step keeps the app shippable.
- Ship on day one. The web UI runs in a DOM-component shell (step 3) before anything is nativized — that's the milestone; everything after is polish.
- Strangle by value. Nativize the hot screens; leave the rest in the webview. Each DOM screen carries a ~2 MB web runtime — reason enough not to ship everything as DOM.
- Nativize means redesign, not reskin. A strangled screen should look like Apple/Google shipped it, not the web page reskinned. Reach for
@expo/uifirst — it renders real SwiftUI/Compose, so it feels exactly like the OS; styled RN primitives are the fallback for custom layouts only. Plus platform navigation (building-native-ui: NativeTabs, large titles), liquid glass and native components via@expo/ui, and mobile UX (sheets, swipe, haptics). The web→native pattern map is./references/native-patterns.md. If it still feels like a website, you ported instead of redesigned. - Verify by running, not compiling. A clean build proves nothing (a blank webview compiles fine). Run each screen — but judge content and behavior against the web original, not pixels (a nativized screen should look more native, not identical).
- Orchestrate, don't reinvent. Each step routes into an existing skill. The value here is the order and the gotchas — the idiom-by-idiom mappings live in
./references/false-friends.md.
Run it as a loop (recommended)
The migration is a long repeat-until-done loop, so the first move is to write the goal objective and launch it — not to grind screens by hand. Fill the objective in ./references/run-as-goal.md for this app and present it; it re-reads this skill every iteration, so each /goal turn reloads the playbook + worklist and drives the next screen (it even self-bootstraps the assess step). Then run /goal with it — or, if the harness can't loop, write it to migration-goal.md and have the user launch it. The steps below are what each iteration does; run them by hand only if you're not looping.
The migration
No repo to migrate — just building native fresh as a web dev? You don't need these steps: use
building-native-ui, and keep./references/false-friends.mdopen for the web→native idiom map. Everything below assumes an existing web app.
1. Assess → write the worklist
Read the repo and produce migration-progress.md, the durable worklist the rest of the migration checks off. Make two cuts:
- Screens vs backend. Page routes (
page.tsx) are screens you migrate; server routes (route.ts), the ORM, and auth handlers stay server-side. Decide the backend once: keep it deployed (the native app becomes an HTTP client) or move it to EAS Hosting (expo-api-routes). - Bucket each screen by how it should land: port-as-is (presentational → ships in a DOM webview), nativize-now (hot, or needs native feel — gestures, lists, keyboard), nativize-later, or hybrid (a native shell around a web sub-tree, e.g. a chat list wrapping a markdown renderer).
Note the framework signals as you read — RSC vs client, Tailwind/shadcn, where data is fetched — since they decide how each screen ports (false-friends has the mappings; async Server Components in particular must be split into a client fetch + a presentational component before they can move). Flag third-party services/SDKs too — browser SDKs don't carry over (false-friends → Services & SDKs); payments especially is a fork, not a swap (in-app digital goods must use store IAP via RevenueCat, ~30% — not Stripe), a business-model call to make now, not at App Store review. The worklist is only trustworthy once every route is sorted and every screen bucketed.
2. Scaffold the shell
create-expo-app, then mirror the web routes in Expo Router — Next's tree maps almost 1:1 (note [id]/page.tsx → [id].tsx, and routes may live in src/app/). Empty screens, one per route.
3. Shell it in DOM components — the day-one milestone
Bring every screen over as a DOM component ('use dom', per the use-dom skill) rendered by its native route, so the whole app runs on a phone before anything is nativized. Expect per-screen edits — unwrapping Server Components, swapping framework imports (next/link), carrying the styling over — all covered in false-friends. Then verify by running (below); this is shippable to TestFlight as-is.
4. Strangle screens to native — by value
Walk migration-progress.md top-down. For each screen, redesign it native — don't port the web layout. Reach for @expo/ui first (real SwiftUI/Compose — buttons, lists, sheets, pickers, sliders; ./references/native-patterns.md maps which web pattern becomes which native component), then platform navigation (building-native-ui — NativeTabs, large titles) and mobile UX (swipe, haptics, momentum/inverted scroll); RN primitives only for custom layouts. Consult ./references/false-friends.md for each idiom. @expo/ui and DOM components both run in Expo Go (SDK 56+) — a dev build (the expo-dev-client skill) is only needed for custom native modules. Verify content and behavior against the running web original (the look should become more native), then check it off. One screen per pass, app shippable throughout. It's a loop over a durable worklist, so it can run unattended — hand it to a goal loop (./references/run-as-goal.md).
5. Wire data, auth, and storage
The web data layer doesn't survive the move — relative fetches, cookie sessions, localStorage, and env vars all change (swaps in false-friends). Use native-data-fetching for requests and caching; add expo-api-routes if the backend moved to EAS Hosting.
6. Ship
expo-deployment for the store builds (App Store / Play / TestFlight), EAS Update for OTA pushes after.
Verify by running, not compiling
A green expo export proves a screen bundles, not that it renders — a screen can build and still render blank or mis-render. So after the shell and after every nativized screen, compare the two running apps for the same route:
- Web original — capture it with
agent-browser(vercel-labs CLI):openthe route,snapshot --jsonthe accessibility tree,screenshot. - Native — drive the simulator with
argent:describe/debugger-component-treefor structure,flowto replay the check each pass.
Pass on parity of content and behavior — not pixels: a nativized screen should look more native than the web, never identical (the DOM-shell stage is the exception — there it is the web UI, so it should match). Feel is part of native and can't be screenshotted — for screens with transitions or gestures, capture a short recording, not just a still (see native-patterns.md → Feel). This loop is opinionated about its tooling: if agent-browser or argent isn't installed, ask the user and install it before proceeding — don't fall back to manual screenshots. Full recipe and setup in ./references/verify-on-device.md.
References
./references/false-friends.md— web idiom → native equivalent + the gotcha for each. The lookup for steps 3–5, and for any web dev unlearning idioms../references/native-patterns.md— web UX pattern → native redesign (@expo/ui-first). The step-4 redesign playbook so screens feel OS-native, not reskinned../references/verify-on-device.md— the two-agent parity recipe: drive the web app (browser agent) and the native app (argent), open the same route, compare../references/run-as-goal.md— a ready-shaped, migration-specific goal objective for driving step 4 unattended (re-reads this skill each iteration).- Expo — From Web to Native with React — the canonical guide this skill operationalizes.
原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。