Catalog
expo/expo-web-to-native

expo

expo-web-to-native

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 `expo-dom` skill for the DOM-component mechanism itself.

NewUpdated Oct 3, 2026

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/>· 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]

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/ui first - 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 (expo-router: 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.

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 expo-router, and keep ./references/false-friends.md open 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 (eas-hosting).
  • 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 expo-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 (expo-router - 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 expo-data-fetching for requests and caching; add eas-hosting if the backend moved to EAS Hosting.

6. Ship

eas-app-stores 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): open the route, snapshot --json the accessibility tree, screenshot.
  • Native — drive the simulator with argent: describe / debugger-component-tree for structure, flow to 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

Submitting Feedback

If you encounter errors, misleading or outdated information in this skill, report it so Expo can improve:

npx --yes submit-expo-feedback@latest --category skills --subject "expo-web-to-native" "<actionable feedback>"

Only submit when you have something specific and actionable to report. Include as much relevant context as possible. If an AI agent repeatedly failed or the user had to take over an Expo task, load the expo-skill-feedback skill and follow its eval-candidate flow instead of reusing the command above.

Files6
6 files · 23.2 KB

Select a file to preview

Overall Score

87/100

Grade

A

Excellent

Grades are signals, not a certification. Always review a skill yourself before use.

Safety

88

Quality

88

Clarity

87

Completeness

84

Summary

A comprehensive guide for migrating existing web React apps (Next.js, Vite, CRA) to native iOS/Android using Expo. The skill operationalizes a strangler-fig pattern: stand up a native shell, run the entire web UI in it on day one via DOM components, then systematically redesign and replace screens to native in priority order. It orchestrates a 6-step workflow, delegating implementation details to specialized companion skills while maintaining overall direction and gotcha-avoidance via structured reference files.

Static Analysis Findings

1 finding

Patterns detected by deterministic static analysis before AI scoring. Hover over any finding code for detailed information and remediation guidance.

Credential Exposure
SEC-020Direct .env File Access2x in 1 file

Direct .env file access

references/false-friends.md.env2x

Detected Capabilities

file read (project analysis)file write (worklist generation, progress tracking)code generation (scaffold Expo shell)build execution (expo export, dev builds)process execution (simulator/device management)external tool invocation (argent, agent-browser for verification)git operations (implied for project introspection)

Trigger Keywords

Phrases that agents use to match this skill to user intent.

migrate web to nativestrangler fig patternexpo appreact native shellweb app native portnext.js to ios android

Risk Signals

INFO

Direct .env file access referenced in false-friends.md for illustrative purposes only

references/false-friends.md
INFO

References expo.dev domain for canonical migration guide

SKILL.md line ~9
INFO

Instructs agent to read third-party service SDKs and assess migration patterns

SKILL.md section 1, 'Assess'

Referenced Domains

External domains referenced in skill content, detected by static analysis.

expo.dev

Use Cases

  • Migrate a Next.js web app to native iOS/Android with Expo as a single mobile shell
  • Port a Vite or Create React App codebase to React Native while reusing web components incrementally
  • Design a strangler-fig migration strategy for large web projects to avoid big-bang rewrites
  • Map web idioms (DOM, CSS, React Router, localStorage) to their native React Native equivalents
  • Execute a screen-by-screen nativization loop with clear prioritization and daily-shippable milestones
  • Verify migrated screens against running web originals to ensure content and behavior parity

Quality Notes

  • Well-structured with clear phase separation (assess → scaffold → DOM shell → nativize → wire data → ship) that maps to a durable worklist
  • Excellent reference architecture: four supporting files (false-friends.md, native-patterns.md, verify-on-device.md, run-as-goal.md) provide systematic lookups for idiom mapping, UX redesign, and verification without re-explanation in the main skill
  • Strong emphasis on *redesign* not *port*, with opinionated guidance to reach for @expo/ui first (real SwiftUI/Compose) before falling back to styled RN primitives
  • Practical day-one milestone: whole app runs in a DOM-component shell before any nativization, keeping the migration shippable throughout
  • Detailed verification recipe (references/verify-on-device.md) specifies exact tooling (agent-browser, argent) and captures both structure and motion, not just pixels
  • Goal-loop pattern (references/run-as-goal.md) shows how to drive the migration unattended by re-injecting the objective each iteration
  • Clear scope boundaries: backend stays backend (HTTP client model), server routes don't migrate, ORM stays server-side
  • Acknowledges high-impact business decisions early (in-app payments must use IAP, not Stripe — a policy decision, not just an SDK swap)
  • Comprehensive false-friends.md reference (~10 KB) covers elements, events, styling, navigation, React architecture, storage, browser APIs, networking, services, and scrolling — eliminates the need for agents to hunt idiom mappings
  • Explicit tooling requirements: doesn't skip verification with agent-browser/argent, asks users to install if missing, rejects manual screenshot fallback
Model: claude-haiku-4-5-20251001Analyzed: Oct 3, 2026

Reviews

Add this skill to your library to leave a review.

No reviews yet

Be the first to share your experience.

Version History

  1. v2.0

    Contract changed: description

    ✦ AIRemoves framework classification from skill description.

    triggering2026-10-03

    LATEST
  2. v1.1

    Content updated

    ✦ AIAdds feedback submission command npx submit-expo-feedback and refers to expo-skill-feedback skill for eval failures.

    2026-09-09

    View This Version
  3. v1.0

    2026-07-11

    View This VersionInitial version

Use expo/expo-web-to-native in your dev environment

Command Palette

Search for a command to run...