Skip to main content
skill Claude Code CX Codex Cursor GitHub Copilot

expo-web-to-native — Claude Code skill: web to native

Guides an existing React web app into a native iOS/Android app with Expo. Use it for a staged screen-by-screen migration.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add expo/skills --skill expo-web-to-native -a claude-code
Open on GitHub

What expo-web-to-native does

This skill orders the migration from assessment to shipping. It sorts routes and screens, scaffolds an Expo Router shell, runs the web UI inside DOM components, and then moves priority screens to native one at a time.

For native screens, the guide points to @expo/ui first, followed by expo-router navigation and mobile patterns such as swipes, sheets, and haptics. Next.js routes map to Expo Router files, while server routes, the ORM, and auth handlers stay on the server.

It assumes an existing web repository. Browser SDKs do not carry over directly, and digital-goods payments need a separate store IAP and RevenueCat decision. Check those dependencies before choosing a migration order.

What it helps with

  • assess React routes before starting the migration
  • sort screens into practical migration stages
  • scaffold a native Expo Router shell
  • run web screens through DOM components
  • redesign priority screens for iOS and Android

Example prompts for the agent

  • Assess this React repository and create `migration-progress.md` with a bucket for every screen.
  • Set up Expo Router for the routes from `page.tsx`, then move the first screen into a DOM component.
  • Take the next screen in `migration-progress.md`, redesign it for native UI, and compare its behavior with the web version.

How to install expo-web-to-native

1Skills CLI (Vercel)

npx skills add expo/skills --skill expo-web-to-native -a claude-code

2Manually: put the files in place

~/.claude/skills/expo-web-to-native/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/expo-web-to-native/

From the same set20

The whole set →
expo-dev-client expo Skill for building and distributing custom Expo development clients to physical devices via local builds or TestFlight. Use it when your app includes native code, Apple widgets, or third-party native modules not in Expo Go. Claude Code CX Codex Cursor +1 70,000installs expo-module expo Complete reference for building native modules and view components in Swift, Kotlin, and TypeScript for Expo apps. Use when writing a new module or adding native functionality—camera, sensors, system APIs. Claude Code CX Codex Cursor +1 44,900installs expo-ui expo A skill for building interfaces that compile to real native code: SwiftUI on iOS, Jetpack Compose on Android. Helps the agent use the right component instead of reaching for Reanimated or heavy community libraries. Claude Code CX Codex Cursor +1 31,600installs expo-native-ui expo Guides your coding agent in building native-feeling Expo screens with Apple HIG styling, @expo/ui components, SF Symbols, and semantic colors. Claude Code CX Codex Cursor +1 31,400installs expo-router expo Skill for file-based routing, stacks, modals and tabs in Expo Router. Use it when building navigation in a React Native mobile app. Claude Code CX Codex Cursor +1 27,700installs expo-brownfield expo Embeds React Native and Expo into existing native iOS and Android apps. Useful when adding cross-platform screens to SwiftUI, UIKit, or Kotlin hosts. Claude Code CX Codex Cursor +1 27,700installs expo-data-fetching expo Configures data fetching, caching, and screen states in Expo apps. Guides network calls with expo/fetch and TanStack Query. Claude Code CX Codex Cursor +1 27,200installs expo-examples expo Adapts official integration patterns from the expo/examples repository or scaffolds fresh projects with npx create-expo. Claude Code CX Codex Cursor +1 26,200installs

Similar

mobile-design sickn33 Reviews and guides iOS, Android, React Native, and Flutter interfaces for touch targets, offline handling, and rendering performance. Claude Code CX Codex Cursor +1 3,700installs argent-screen-recording software-mansion Captures video of an iOS simulator or Android emulator screen as h264 mp4 with touch visualization overlaid. Use it to document app flows, animations, and interactions as self-explanatory video clips. Claude Code CX Codex Cursor +1 17,700installs dart-use-primary-constructors flutter Guides AI assistants in writing and refactoring Dart primary constructors. Useful when upgrading Dart codebases to Dart 3.13+ syntax and cutting class boilerplate. Claude Code CX Codex Cursor +1 14,000installs dart-setup-ffi-assets flutter Skill guides agents in automating compilation and packaging of C/C++ code into dynamic or static libraries via Dart's Native Assets system. Use it to integrate native code into Flutter or Dart packages. Claude Code CX Codex Cursor +1 13,800installs vercel-react-native-skills vercel A rules pack for React Native and Expo work: lists, animations, navigation, native modules. Load it so your agent makes mobile changes the same way every time. Claude Code CX Codex Cursor +1 253,900installs animate-expo emilkowalski Builds React Native and Expo animations using Reanimated and Gesture Handler. Helps keep motion on the UI thread and avoid dropped frames on physical devices. Claude Code CX Codex Cursor +1 135,900installs mobile-native emilkowalski Strips the tells that give a web app away on a phone: sticky hover, tap highlight flash, the 100vh bug. Claude Code CX Codex Cursor +1 84,500installs aso coreyhaines31 Audits App Store and Google Play listings against store optimization standards. Helps uncover gaps in metadata, visual assets, and conversion. Claude Code CX Codex Cursor +1 74,900installs

Questions about expo-web-to-native

How do I install `expo-web-to-native` in Claude Code?

Run npx skills add expo/skills --skill expo-web-to-native -a claude-code, then run npx skills add expo/skills --skill expo-web-to-native -a claude-code again. The skill file is available at ~/.claude/skills/expo-web-to-native/SKILL.md.

What does `expo-web-to-native` do?

It guides the migration of an existing React web app into an Expo native app, from route assessment and shell setup to screen-by-screen native work and behavior checks.

Do I need a separate dev build to use this skill?

@expo/ui and DOM components run in Expo Go with SDK 56+. A dev build with expo-dev-client is needed for custom native modules.

Source text

SKILL.md · MIT
---
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 @expo/agent-cli@latest feedback --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 agent repeatedly fails at an Expo task or the user has to step in, submit a brief report of the task, expected result, and what went wrong using `--category evals` and the relevant Expo package or command as `--subject`.

© expo · MIT · expo/skills

See also

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor