expo-web-to-native
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWeb to Native
Web 转原生应用
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.
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]Web React应用并非转换为原生应用——不存在这样的转译器。它是逐步迁移的,逐个屏幕进行,就像绞杀榕缠绕树木并慢慢取代它的过程:先搭建一个原生外壳,第一天就在其中运行完整的Web UI,然后按照优先级逐个将屏幕重构为原生实现。本技能是迁移工作的核心框架;每个步骤都会衔接至现有的Expo技能,而非重复讲解细节。它将Expo的《使用React从Web转向原生》指南落地为可执行的步骤——若想了解迁移的原因,请阅读该文章。
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 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/ui: NativeTabs, large titles), liquid glass and native components viaexpo-router, and mobile UX (sheets, swipe, haptics). The web→native pattern map is@expo/ui. If it still feels like a website, you ported instead of redesigned../references/native-patterns.md - 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
- 迁移而非重写:绝不采用大爆炸式重构;每一步都要保证应用可发布。
- 首日即可发布:在完成任何原生重构前,Web UI就可以在DOM组件外壳中运行(步骤3)——这是关键里程碑;后续所有工作都是优化。
- 按价值优先级重构:优先对高频使用的屏幕进行原生重构;其余屏幕保留在Web视图中。每个DOM屏幕会占用约2MB的Web运行时——这足以成为不将所有内容都以DOM形式发布的理由。
- 原生重构意味着重新设计,而非换皮:重构后的屏幕应具备苹果/谷歌官方应用的质感,而非Web页面的换皮版本。优先使用——它会渲染真正的SwiftUI/Compose组件,因此完全贴合系统风格;只有在需要自定义布局时,才退而求其次使用RN基础组件。此外还需适配平台导航(
@expo/ui:NativeTabs、大标题)、液态玻璃效果及通过expo-router实现的原生组件,以及移动端UX(底部弹窗、滑动操作、触觉反馈)。Web→原生的模式映射可查看@expo/ui。如果最终效果仍像网站,说明你只是移植而非重新设计。./references/native-patterns.md - 通过运行验证,而非编译:成功编译不代表任何问题(空白Web视图也能正常编译)。要运行每个屏幕进行验证——但需对照Web原版验证内容和行为,而非像素(原生重构后的屏幕应更具原生质感,而非与Web版完全一致)。
- 统筹协调,而非重复造轮子:每个步骤都会衔接至现有技能。本技能的价值在于提供步骤顺序和注意事项——Web与原生惯用法的映射可查看。
./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 for this app and present it; it re-reads this skill every iteration, so each turn reloads the playbook + worklist and drives the next screen (it even self-bootstraps the assess step). Then run with it — or, if the harness can't loop, write it to and have the user launch it. The steps below are what each iteration does; run them by hand only if you're not looping.
./references/run-as-goal.md/goal/goalmigration-goal.md迁移是一个需要重复执行直至完成的长期过程,因此第一步是撰写目标并启动——而非手动逐个处理屏幕。为本应用填写中的目标并启动;它会在每次迭代时重新读取本技能,因此每次执行都会重新加载指南和任务清单,并推进下一个屏幕的迁移(甚至可以自动完成评估步骤的初始化)。之后使用该目标执行;如果无法循环执行,可将目标写入并让用户启动。以下是每次迭代的具体步骤;仅当无法循环时才手动执行。
./references/run-as-goal.md/goal/goalmigration-goal.mdThe migration
迁移步骤
No repo to migrate - just building native fresh as a web dev? You don't need these steps: use, and keepexpo-routeropen for the web→native idiom map. Everything below assumes an existing web app../references/false-friends.md
无现有仓库可迁移 - 作为Web开发者从零开始构建原生应用?你无需遵循以下步骤:直接使用,并打开expo-router查看Web→原生惯用法映射。以下所有步骤均假设存在现有Web应用。./references/false-friends.md
1. Assess → write the worklist
1. 评估 → 撰写任务清单
Read the repo and produce , the durable worklist the rest of the migration checks off. Make two cuts:
migration-progress.md- Screens vs backend. Page routes () are screens you migrate; server routes (
page.tsx), 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 (route.ts).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 ( → 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.
false-friends读取仓库内容并生成,这是后续迁移工作的持久任务清单。需完成两项梳理:
migration-progress.md- 屏幕与后端分离:页面路由()是需要迁移的屏幕;服务器路由(
page.tsx)、ORM和认证处理程序保留在服务器端。只需确定一次后端方案:保持现有部署(原生应用作为HTTP客户端)或迁移至EAS Hosting(route.ts)。eas-hosting - 为每个屏幕分类:确定每个屏幕的处理方式:原样移植(展示型页面→以DOM Web视图形式发布)、立即原生重构(高频使用或需要原生体验——手势、列表、键盘交互)、后续原生重构,或混合模式(原生外壳包裹Web子树,例如聊天列表包裹markdown渲染器)。
读取时记录框架特征——RSC与客户端组件、Tailwind/shadcn、数据获取位置——这些将决定每个屏幕的移植方式(文档中有对应映射;尤其是异步Server Components,必须拆分为客户端获取组件+展示组件后才能迁移)。同时标记第三方服务/SDK——浏览器SDK无法直接迁移(→Services & SDKs章节);支付功能尤其需要重新实现,而非替换(应用内数字商品必须使用应用商店的IAP,通过RevenueCat实现,抽成约30%——不能使用Stripe),这是需要提前确定的业务决策,而非等到应用商店审核时才处理。只有当所有路由都已梳理、所有屏幕都已分类后,任务清单才具备可信度。
false-friendsfalse-friends2. Scaffold the shell
2. 搭建原生外壳
create-expo-app[id]/page.tsx[id].tsxsrc/app/执行,然后在Expo Router中镜像Web应用的路由——Next.js的路由树几乎可以1:1映射(注意→,路由可能位于目录)。为每个路由创建空屏幕。
create-expo-app[id]/page.tsx[id].tsxsrc/app/3. Shell it in DOM components — the day-one milestone
3. 集成DOM组件外壳——首日里程碑
Bring every screen over as a DOM component (, per the 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 (), carrying the styling over - all covered in false-friends. Then verify by running (below); this is shippable to TestFlight as-is.
'use dom'expo-domnext/link将所有屏幕以DOM组件形式(遵循技能中的规范)接入对应的原生路由,这样在完成任何原生重构前,整个应用就能在手机上运行。预计需要对每个屏幕进行编辑——拆分Server Components、替换框架导入(如)、迁移样式——这些都在文档中有说明。然后通过运行验证(见下文);此时应用已可发布至TestFlight。
expo-dom'use dom'next/linkfalse-friends4. Strangle screens to native — by value
4. 按价值优先级重构屏幕为原生实现
Walk top-down. For each screen, redesign it native - don't port the web layout. Reach for first (real SwiftUI/Compose - buttons, lists, sheets, pickers, sliders; maps which web pattern becomes which native component), then platform navigation ( - NativeTabs, large titles) and mobile UX (swipe, haptics, momentum/inverted scroll); RN primitives only for custom layouts. Consult for each idiom. and DOM components both run in Expo Go (SDK 56+) - a dev build (the 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 ().
migration-progress.md@expo/ui./references/native-patterns.mdexpo-router./references/false-friends.md@expo/uiexpo-dev-client./references/run-as-goal.md按照的优先级顺序处理。对于每个屏幕,重新设计原生版本,而非移植Web布局。优先使用(提供真正的SwiftUI/Compose组件——按钮、列表、底部弹窗、选择器、滑块;映射了Web模式对应的原生组件),然后适配平台导航(——NativeTabs、大标题)和移动端UX(滑动操作、触觉反馈、惯性/反向滚动);仅在需要自定义布局时使用RN基础组件。每个惯用法的处理可参考。和DOM组件均可在Expo Go(SDK 56+)中运行——只有在使用自定义原生模块时才需要开发构建版本(技能)。对照运行中的Web原版验证内容和行为(外观应更具原生质感),然后标记为完成。每次处理一个屏幕,全程保证应用可发布。这是一个基于持久任务清单的循环过程,可无人值守执行——将其交给目标循环()即可。
migration-progress.md@expo/ui./references/native-patterns.mdexpo-router./references/false-friends.md@expo/uiexpo-dev-client./references/run-as-goal.md5. Wire data, auth, and storage
5. 对接数据、认证与存储
The web data layer doesn't survive the move - relative fetches, cookie sessions, , and env vars all change (swaps in false-friends). Use for requests and caching; add if the backend moved to EAS Hosting.
localStorageexpo-data-fetchingeas-hostingWeb应用的数据层无法直接迁移——相对路径请求、Cookie会话、和环境变量均需调整(文档中有对应的替换方案)。使用处理请求和缓存;如果后端已迁移至EAS Hosting,则添加配置。
localStoragefalse-friendsexpo-data-fetchingeas-hosting6. Ship
6. 发布
eas-app-stores使用构建应用商店版本(App Store / Play Store / TestFlight),发布后使用EAS Update进行OTA推送更新。
eas-app-storesVerify by running, not compiling
通过运行验证,而非编译
A green 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:
expo export- Web original — capture it with (vercel-labs CLI):
agent-browserthe route,openthe accessibility tree,snapshot --json.screenshot - Native — drive the simulator with :
argent/describefor structure,debugger-component-treeto replay the check each pass.flow
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 → Feel). This loop is opinionated about its tooling: if or isn't installed, ask the user and install it before proceeding — don't fall back to manual screenshots. Full recipe and setup in .
native-patterns.mdagent-browserargent./references/verify-on-device.mdexpo export- Web原版——使用****(vercel-labs CLI)捕获:
agent-browser打开路由,open获取无障碍树,snapshot --json截图。screenshot - 原生版本——使用****驱动模拟器:
argent/describe查看结构,debugger-component-tree在每次迭代时重复验证流程。flow
验证通过的标准是内容和行为一致——而非像素完全相同:原生重构后的屏幕应比Web版更具原生质感,而非完全一致(DOM外壳阶段除外——此时就是Web UI,因此应与Web版完全匹配)。原生体验的触感无法通过截图捕捉——对于带有过渡动画或手势的屏幕,需录制短视频而非仅截图(见→Feel章节)。本流程对工具选型有明确要求:如果未安装或,请告知用户并先安装——不要退而求其次使用手动截图。完整的配置和流程可查看。
native-patterns.mdagent-browserargent./references/verify-on-device.mdReferences
参考资料
- — web idiom → native equivalent + the gotcha for each. The lookup for steps 3–5, and for any web dev unlearning idioms.
./references/false-friends.md - — web UX pattern → native redesign (
./references/native-patterns.md-first). The step-4 redesign playbook so screens feel OS-native, not reskinned.@expo/ui - — the two-agent parity recipe: drive the web app (browser agent) and the native app (argent), open the same route, compare.
./references/verify-on-device.md - — a ready-shaped, migration-specific goal objective for driving step 4 unattended (re-reads this skill each iteration).
./references/run-as-goal.md - Expo — From Web to Native with React — the canonical guide this skill operationalizes.
- ——Web惯用法→原生等效方案及注意事项。是步骤3-5的参考手册,也是Web开发者摒弃旧惯用法的指南。
./references/false-friends.md - ——Web UX模式→原生重设计方案(优先使用
./references/native-patterns.md)。是步骤4的重设计指南,确保屏幕具备系统原生质感而非换皮效果。@expo/ui - ——双代理一致性验证流程:驱动Web应用(浏览器代理)和原生应用(argent),打开同一路由并进行对比。
./references/verify-on-device.md - ——预定义的迁移专用目标,用于无人值守执行步骤4(每次迭代时重新读取本技能)。
./references/run-as-goal.md - Expo — 使用React从Web转向原生——本技能落地的官方指南。