expo-web-to-native
This skill guides migrating an existing web React app to native iOS/Android using Expo through a strangler fig pattern.
Identity proven by GitHub's verified organisation badge (expo)
- Agents
- Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode
- License
- MIT (permissive)
- Pin
- d4f4840pinned Oct 9, 2026
- Context cost
- about 2,400 tokenswhen the skill loads
- GitHub stars
- 2,682on expo/skills
- Scripts
- None found
What it does
“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.”
Generated summary from the skill's own text
This skill guides migrating an existing web React app to native iOS/Android using Expo through a strangler fig pattern. It provides the step-by-step sequence to scaffold an Expo shell, wrap the web UI in DOM components on day one, then incrementally redesign hot screens to native with @expo/ui and Expo Router. The skill orchestrates the migration as a durable worklist, referencing related skills for DOM components, native patterns, data fetching, and app store builds.
When to use it
- Porting a Next.js, Vite, or Create React App codebase to React Native mobile.
- Turning an existing website into a native iOS/Android app incrementally.
- Learning how web patterns like localStorage, React Router, CSS map to native equivalents.
- Running a long migration project that needs to stay shippable at each step.
When not to
- Building a native app from scratch without an existing web codebase to migrate.
- Needing detailed DOM-component implementation, use the expo-dom skill for that mechanism.
- Converting a web app with a transpiler, this is a screen-by-screen redesign approach, not conversion.
Install
These commands install from the publisher's repository and need no account.
skills CLI
npx skills add expo/skills --skill expo-web-to-nativeInstalls from the repository's default branch, which may have changed since the commit we checked.
GitHub CLI 2.90 or later
gh skill install expo/skills expo-web-to-native@d4f484024fec15196bfd3c272e953e3f983972cfInstalls the commit we checked (d4f4840).
Copy by hand
Download the folder at d4f4840 and put it in .claude/skills/expo-web-to-native for Claude Code and in .agents/skills/expo-web-to-native for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode.
Claude Code plugin
/plugin install expo@claude-plugins-officialThe publisher's plugin in Claude's official marketplace. It may hold more than this skill, and we have not pinned it.
Copy for your agent
A prompt for any coding agent. It installs this skill from GitHub at the pinned commit, copies the files as they are, and runs nothing unless you ask.
Prompt
Install the agent skill "expo-web-to-native" by Expo. Source, pinned to commit d4f484024fec15196bfd3c272e953e3f983972cf: https://github.com/expo/skills/tree/d4f484024fec15196bfd3c272e953e3f983972cf/plugins/expo/skills/expo-web-to-native 1. Download that folder at exactly this commit. Do not use the default branch. 2. Copy every file unchanged, keeping the folder structure, into .claude/skills/expo-web-to-native/ for Claude Code and .agents/skills/expo-web-to-native/ for Codex, Cursor, Gemini CLI, GitHub Copilot and OpenCode. Create the folders if they do not exist. 3. Do not edit the files, and do not run any script in the folder unless I ask you to. 4. When you are done, list the files you copied and where you put them. Listing and checks: https://www.agentnative.dev/skills/expo/expo-web-to-native
SKILL.md at the pin
---
name: expo-web-to-native
description: 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.
version: 1.0.0
license: MIT
---
# 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](https://expo.dev/blog/from-web-to-native-with-react) — read that for the why.
```mermaid
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`](./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`](./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`](./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`](./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`](./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`](./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`](./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/verify-on-device.md).
## References
- [`./references/false-friends.md`](./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`](./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`](./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`](./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](https://expo.dev/blog/from-web-to-native-with-react) — the canonical guide this skill operationalizes.
## Submitting Feedback
If you encounter errors, misleading or outdated information in this skill, report it so Expo can improve:
```bash
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.
From expo/skills at d4f4840, shown as plain text under its license: MIT (permissive). The license text is in plugins/expo/skills/expo-web-to-native/SKILL.md.
View SKILL.md on GitHub at d4f4840Files
- SKILL.md10 KB · 8944274295f7
- agents/
- openai.yaml406 B · 94f4c4510b2f
- references/
- false-friends.md10 KB · a6e526bee7e2
- native-patterns.md4.4 KB · ca222d569448
- run-as-goal.md3.7 KB · f565f2fd5ca1
- verify-on-device.md4.0 KB · 8f8212088be2
6 files, 33 KB in total. Each file has a sha256 in the pin's manifest; the first 12 characters are shown.
Skill folder on GitHub at d4f4840More from Expo
expo-app-intents
This skill adds Apple App Intents to Expo apps, enabling Siri voice commands and Shortcuts automation through Swift declarations that dispatch to JavaScript…
Claude Code, Codex, Cursor +4 · about 3.6K tokens · pinned 7 days ago
expo-sqlite
This skill enables SQLite database operations in Expo and React Native apps for structured data storage, offline queues, and persistent caches.
Claude Code, Codex, Cursor +4 · about 2.6K tokens · pinned 7 days ago
eas-app-stores
This skill configures and executes iOS and Android app builds and store submissions using EAS, Expo's app distribution service.
Claude Code, Codex, Cursor +3 · about 1.7K tokens · pinned 7 days ago
eas-hosting
This skill deploys Expo web applications and API routes to EAS Hosting, a managed edge platform running on Cloudflare Workers.
Claude Code, Codex, Cursor +3 · about 2.7K tokens · pinned 7 days ago
eas-observe
EAS Observe tracks startup, navigation, and custom-event performance in production Expo apps through instrumentation (AppMetricsRoot/ObserveRoot wrapper…
Claude Code, Codex, Cursor +3 · about 1.1K tokens · pinned 7 days ago
eas-simulator
Runs a user's app on a remote iOS or Android simulator hosted on EAS cloud infrastructure, accessible from machines without local simulator support like Linux…
Claude Code, Codex, Cursor +3 · about 6.3K tokens · pinned 7 days ago
Related in Mobile
expo-animation
This skill guides building animations in React Native and Expo apps using Reanimated, Gesture Handler, and Expo Router.
by ExpoOfficial publisher
Claude Code, Codex, Cursor +3 · about 4.6K tokens · pinned 7 days ago
expo-brownfield
This skill helps agents add React Native to existing native iOS or Android apps incrementally using Expo.
by ExpoOfficial publisher
Claude Code, Codex, Cursor +4 · about 1.4K tokens · pinned 7 days ago
expo-data-fetching
This skill guides implementing network requests, API calls, and data fetching in Expo apps using fetch, React Query, SWR, and Expo Router loaders.
by ExpoOfficial publisher
Claude Code, Codex, Cursor +3 · about 3.3K tokens · pinned 7 days ago
expo-overview
Routes Expo and EAS tasks to the correct specialized skill by detecting the user's goal and project type.
by ExpoOfficial publisher
Claude Code, Codex, Cursor +3 · about 2K tokens · pinned 7 days ago
add-connector
This skill adds Power Platform connectors to Expo/React Native mobile apps by discovering the connector, establishing a connection, generating service files…
by MicrosoftOfficial publisher
Claude Code · about 3.8K tokens · pinned 7 days ago
create-mobile-app
This skill creates a new mobile app using Power Apps, Expo, React Native, and TypeScript targeted at iOS and Android platforms.
by MicrosoftOfficial publisher
Claude Code · about 51.1K tokens · pinned 7 days ago