web-to-native

Par expo · skills

Migrez une application web React existante vers une application native iOS/Android avec Expo. À utiliser quand l'utilisateur souhaite transformer un site web en application mobile, porter une base de code React Next.js/Vite/CRA vers React Native, réutiliser du code web sur natif de façon incrémentale, ou demande comment les idiomes web (le DOM, CSS, React Router, localStorage, window) s'appliquent au natif. Il s'agit du guide de migration de bout en bout ; utilisez le skill `use-dom` pour le mécanisme des composants DOM lui-même.

npx skills add https://github.com/expo/skills --skill web-to-native

Web vers natif

Une app React web ne se convertit pas en natif — il n'existe pas de transpileur. Elle migre, écran par écran, comme un figuier étrangleur qui pousse autour d'un arbre et le remplace peu à peu : créer une enveloppe native, lancer toute l'UI web dedans dès le jour un, puis étouffer chaque écran pour le rendre natif par ordre de priorité. Cette skill ordonne le travail ; chaque étape se transmet à une skill Expo existante plutôt que de la ré-expliquer. Elle opérationnalise From Web to Native with React d'Expo — lis-le pour comprendre le pourquoi.

flowchart TD
    A1[1 · Évaluer : rédiger la liste de travail] --> A2[2 · Échafauder l'enveloppe Expo]
    A2 --> A3[3 · Enveloppe composants DOM<br/>· use-dom · JOUR UN]
    A3 --> A4[4 · Étouffer les écrans en natif<br/>priorité la plus élevée · building-native-ui]
    A4 -->|plus d'écrans| A4
    A4 --> A5[5 · Connecter données / auth / stockage<br/>· native-data-fetching]
    A5 --> A6[6 · Lancer · expo-deployment]

Principes

  • Migrer, ne pas réécrire. Ne jamais tout faire d'un coup ; chaque étape garde l'app en état de lancer.
  • Lancer le jour un. L'UI web s'exécute dans une enveloppe composants DOM (étape 3) avant que rien ne soit nativifié — c'est le jalon ; tout ce qui suit est du polissage.
  • Étouffer par valeur. Nativifier les écrans hot ; laisser le reste dans la webview. Chaque écran DOM porte un runtime web d'environ 2 MB — raison suffisante de ne pas tout lancer en DOM.
  • Nativifier signifie redesigner, pas reskin. Un écran étouffé devrait ressembler à ce qu'Apple/Google aurait livré, pas à la page web restylisée. Utilise d'abord @expo/ui — il affiche du vrai SwiftUI/Compose, donc c'est exactement comme l'OS ; les primitives RN stylisées sont le fallback pour les layouts custom seulement. Plus la navigation par plateforme (building-native-ui : NativeTabs, large titles), le verre liquide et les composants natifs via @expo/ui, et l'UX mobile (sheets, swipe, haptics). La carte des motifs web→natif est dans ./references/native-patterns.md. Si ça ressemble toujours à un site, tu as porté au lieu de redesigner.
  • Vérifier en exécutant, pas en compilant. Un build propre ne prouve rien (une webview vide compile très bien). Exécute chaque écran — mais juge le contenu et le comportement par rapport à l'original web, pas les pixels (un écran nativifié devrait ressembler plus au natif, pas identique).
  • Orchestrer, ne pas réinventer. Chaque étape bascule vers une skill existante. La valeur ici, c'est l'ordre et les pièges — les mappings idiome-par-idiome vivent dans ./references/false-friends.md.

L'exécuter comme une boucle (recommandé)

La migration est une longue boucle repeat-until-done, donc le premier geste est de rédiger l'objectif global et le lancer — pas de moudre les écrans à la main. Remplis l'objectif dans ./references/run-as-goal.md pour cette app et présente-le ; il relit cette skill à chaque itération, donc chaque tour /goal recharge le playbook + la liste de travail et pilote l'écran suivant (il auto-amorce même l'étape d'évaluation). Ensuite lance /goal avec lui — ou, si le harness ne peut pas boucler, écris-le dans migration-goal.md et fais lancer l'utilisateur. Les étapes ci-dessous sont ce que chaque itération fait ; exécute-les à la main seulement si tu ne boucles pas.

La migration

Pas de repo à migrer — tu construis juste du natif frais en tant que dev web ? Tu n'as pas besoin de ces étapes : utilise building-native-ui, et garde ./references/false-friends.md ouvert pour la carte des idiomes web→natif. Tout ce qui suit suppose une app web existante.

1. Évaluer → rédiger la liste de travail

Lis le repo et produis migration-progress.md, la liste de travail durable que le reste de la migration coche. Fais deux coupures :

  • Écrans vs backend. Les routes de page (page.tsx) sont des écrans que tu migres ; les routes serveur (route.ts), l'ORM, et les handlers d'auth restent côté serveur. Décide le backend une fois : le garder déployé (l'app native devient un client HTTP) ou le déplacer vers EAS Hosting (expo-api-routes).
  • Classe chaque écran selon comment il devrait arriver : port-as-is (présentatif → lancer dans une webview DOM), nativify-now (hot, ou a besoin du ressenti natif — gestes, listes, clavier), nativify-later, ou hybrid (une enveloppe native autour d'une sous-arborescence web, ex. une liste de chat enrobant un renderer markdown).

Note les signaux de framework au fur et à mesure que tu lis — RSC vs client, Tailwind/shadcn, où les données sont récupérées — puisqu'ils décident comment chaque écran se porte (false-friends a les mappings ; les Server Components async en particulier doivent être scindés en un fetch client + un composant présentatif avant de pouvoir bouger). Signale aussi les services/SDKs tiers — les SDKs navigateur ne se reportent pas (false-friendsServices & SDKs) ; les paiements surtout c'est un fork, pas un swap (les biens numériques in-app doivent utiliser l'IAP du magasin via RevenueCat, ~30% — pas Stripe), un appel de modèle économique à faire maintenant, pas à l'examen de l'App Store. La liste de travail n'est fiable qu'une fois que chaque route est triée et chaque écran classé.

2. Échafauder l'enveloppe

create-expo-app, puis refléter les routes web dans Expo Router — l'arborescence de Next se mappe quasi 1:1 (note [id]/page.tsx[id].tsx, et les routes peuvent vivre dans src/app/). Écrans vides, un par route.

3. L'envelopper en composants DOM — le jalon du jour un

Apporte chaque écran en tant que composant DOM ('use dom', selon la skill use-dom) rendu par sa route native, pour que l'app entière s'exécute sur téléphone avant que rien ne soit nativifié. Attends-toi à des édits par écran — dépacker les Server Components, swapper les imports de framework (next/link), porter le styling — tout couvert dans false-friends. Puis vérifie en exécutant (ci-dessous) ; c'est lanç-able sur TestFlight tel quel.

4. Étouffer les écrans en natif — par valeur

Parcours migration-progress.md de haut en bas. Pour chaque écran, redesigne-le natif — ne porte pas le layout web. Utilise d'abord @expo/ui (vrai SwiftUI/Compose — boutons, listes, sheets, pickers, sliders ; ./references/native-patterns.md mappe quel motif web devient quel composant natif), puis la navigation par plateforme (building-native-ui — NativeTabs, large titles) et l'UX mobile (swipe, haptics, momentum/inverted scroll) ; les primitives RN seulement pour les layouts custom. Consulte ./references/false-friends.md pour chaque idiome. @expo/ui et les composants DOM s'exécutent tous deux dans Expo Go (SDK 56+) — un dev build (la skill expo-dev-client) n'est nécessaire que pour les modules natifs custom. Vérifie le contenu et le comportement contre l'original web en exécution (le look devrait devenir plus natif), puis coche-le. Un écran par passe, app lanç-able partout. C'est une boucle sur une liste de travail durable, donc elle peut s'exécuter sans surveillance — confie-la à une boucle goal (./references/run-as-goal.md).

5. Connecter données, auth, et stockage

La couche données web ne survit pas au déménagement — les récupérations relatives, les sessions par cookie, localStorage, et les vars env changent tous (swaps dans false-friends). Utilise native-data-fetching pour les requêtes et le caching ; ajoute expo-api-routes si le backend a déménagé vers EAS Hosting.

6. Lancer

expo-deployment pour les builds du magasin (App Store / Play / TestFlight), EAS Update pour les poussées OTA après.

Vérifier en exécutant, pas en compilant

Un expo export vert prouve qu'un écran se compile, pas qu'il s'affiche — un écran peut builder et s'afficher vide ou mal s'afficher. Donc après l'enveloppe et après chaque écran nativifié, compare les deux apps en exécution pour la même route :

  • Original web — capture-le avec agent-browser (CLI vercel-labs) : open la route, snapshot --json l'arbre d'accessibilité, screenshot.
  • Natif — pilote le simulateur avec argent : describe / debugger-component-tree pour la structure, flow pour rejouer la vérification à chaque passe.

Passe sur la parité du contenu et du comportement — pas les pixels : un écran nativifié devrait ressembler plus au natif que le web, jamais identique (l'étape enveloppe-DOM est l'exception — là c'est l'UI web, donc ça devrait matcher). La sensation fait partie du natif et ne peut pas être capturée en screenshot — pour les écrans avec transitions ou gestes, capture un court enregistrement, pas juste un still (voir native-patterns.md → Feel). Cette boucle est d'opinion sur ses outils : si agent-browser ou argent n'est pas installé, demande à l'utilisateur et l'installe avant de continuer — ne replie pas sur des screenshots manuels. Recette complète et setup dans ./references/verify-on-device.md.

Références

  • ./references/false-friends.md — idiome web → équivalent natif + le piège pour chacun. La lookup pour les étapes 3–5, et pour tout dev web en réapprentissage d'idiomes.
  • ./references/native-patterns.mdmotif UX web → redesign natif (@expo/ui-first). Le playbook de redesign étape-4 pour que les écrans ressemblent au natif d'OS, pas reskinné.
  • ./references/verify-on-device.md — la recette parité deux-agents : pilote l'app web (browser agent) et l'app native (argent), ouvre la même route, compare.
  • ./references/run-as-goal.md — un objectif global prêt à l'emploi, spécifique migration, pour piloter l'étape 4 sans surveillance (relit cette skill à chaque itération).
  • Expo — From Web to Native with React — le guide canonique que cette skill opérationnalise.

Skills similaires