All skills
expo avatar

/expo-web-to-native

@5c8f62e official
by expoexpo/skills2.6k stars
156

Framework (OSS). 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.

Use this Skill: https://skilld.dev/gh/expo/skills/expo-web-to-native

This session only. Nothing lands on disk.

referencesfalse-friends.md

≈2.5k tokens on demand. Your agent reads this file only when SKILL.md points to it.

False Friends: web idioms → native

Disclosed reference for expo-web-to-native. A false friend is a web idiom that looks portable but isn't - it compiles in your head, then breaks (or silently no-ops) on native. Each row is web idiom → native equivalent → the gotcha. Reach for this in steps 3–5 of a migration, and whenever you're learning native after the web.

This is the single source of truth for the mapping; the SKILL.md steps name a few high-traffic swaps but defer the full list here.

Elements & text

Web Native Gotcha
<div>, <section>, <span> <View> View is the only generic container; no semantic element variants.
<p>, <h1>, raw text in JSX <Text> All text must sit inside <Text>. Bare strings in a View throw.
<img src> <Image> / expo-image expo-image adds caching, blurhash, transitions. Remote images need an explicit width/height or a flex parent.
<button>, <a> <Pressable> No default styling, no hover. Wrap label text in <Text>.
<input> <TextInput> Controlled via value + onChangeText (not onChange/e.target.value).
<ul>/<li> rendered with .map() <FlatList> / <FlashList> .map() over a long list mounts every row at once → jank. Use a virtualized list with data + renderItem.

Events & interaction

Web Native Gotcha
onClick onPress Also onLongPress, onPressIn/Out. There is no click.
onChange={e => e.target.value} onChangeText={text => ...} The handler receives the string directly, not an event.
onSubmit (form) onPress / onSubmitEditing No <form> element and no submit event; wire the button/keyboard return key.
:hover, :focus press/focus state No hover on touch. Track state and style from it, or use Pressable's state callback.
e.preventDefault() — No default actions to prevent; remove it.

Styling & layout

Web Native Gotcha
className / CSS files StyleSheet.create or NativeWind Plain RN has no className. For Tailwind muscle memory use NativeWind.
CSS cascade & inheritance none Styles don't inherit (except a few Text props like color/fontSize to nested Text). Style each node.
display: flex opt-in, default row flex always on, default column Biggest layout surprise: flex is the only layout model and the main axis is vertical by default.
px, rem, % unitless numbers (dp) padding: 16 = density-independent pixels. % works in some props; no rem/em/vh.
@media queries useWindowDimensions() / Platform No media queries. Branch on dimensions or platform at runtime.
position: fixed position: absolute + safe area No fixed. Pin with absolute and respect insets (react-native-safe-area-context).
z-index zIndex (+ elevation on Android) Works mostly, but Android shadow/stacking also needs elevation.
CSS transitions / @keyframes Animated / Reanimated No CSS animation. Reanimated is the standard; gestures via react-native-gesture-handler.
gap, flexbox gap, flexbox These largely transfer — flexbox is the one big win for web devs.

Navigation & URLs

Web Native Gotcha
React Router / Next.js routes Expo Router (file-based) File-based like Next App Router; react-router concepts map but the API differs.
<a href> / <Link> expo-router <Link href> Works in DOM components too.
useNavigate() / router.push useRouter() from expo-router router.push/replace/back.
useParams / useSearchParams useLocalSearchParams() Note: doesn't work inside a DOM component - read in the native parent, pass as props (see expo-dom).
window.location usePathname() / router No location object.
Deep links / myapp:// URL scheme + universal links Configure scheme in app config; native gives you real deep linking and (with AASA) app links - see expo-app-clip for the AASA file.

React architecture & data (Next.js / SSR apps)

Web Native Gotcha
Async Server Component (async function Page()) client component + data fetch No RSC on native (Expo's is experimental and different). Split into a client component that fetches via the API + a presentational child before porting.
Server actions ('use server') API route + fetch No server actions. Expose an endpoint and call it.
cookies() / headers() (server) token in expo-secure-store Server-only. Auth moves to a bearer token sent as a header.
ORM / DB client in a page (Drizzle, Prisma) server only - call over HTTP The device never talks to the DB. Keep the backend deployed, or move routes to EAS Hosting (eas-hosting); the app fetches.
next/navigation (notFound, redirect) expo-router redirect() → router.replace; notFound() → a native +not-found route.
_layout as a DOM component always native Layout routes can't be DOM components — they are the native shell. Rewrite web-only layout logic (theme scripts, providers) with native APIs.

Storage, state & sessions

Web Native Gotcha
localStorage / sessionStorage expo-sqlite (globalThis.localStorage) import 'expo-sqlite/localStorage/install'; polyfills the global localStorage, so existing code works as-is (or expo-sqlite/kv-store). No sessionStorage equivalent — native has no session concept.
Cookies / document.cookie expo-secure-store + headers No cookie jar by default. Store tokens in SecureStore; send as auth headers.
Auth via session cookie token in SecureStore Redesign auth around bearer tokens, not browser sessions.
In-memory React state same useState/useReducer/useContext transfer unchanged.

Browser & platform APIs

Web Native Gotcha
window, document none Don't exist. Guard web-only libs, or keep them in a DOM component (expo-dom).
window.matchMedia('(prefers-color-scheme: dark)') useColorScheme() Hook from react-native, reactive to the system theme. Web theme bootstrap scripts in the layout don't run.
window.innerWidth/Height useWindowDimensions() Reactive hook; updates on rotation.
navigator.*, Web APIs Expo SDK modules e.g. camera → expo-camera, geolocation → expo-location, clipboard → expo-clipboard.
alert() / confirm() / prompt() alert() works; Alert.alert(...) for the rest RN provides a global alert(); confirm() / prompt() don't exist — use Alert.alert with a button array.
process.env.X process.env.EXPO_PUBLIC_X Only EXPO_PUBLIC_-prefixed vars are inlined into the client bundle.
console.log console.log Works — shows in the dev tools/terminal.
dangerouslySetInnerHTML — No DOM to inject into. Render data, or use a DOM component / WebView.
inline SVG react-native-svg No native <svg>; the library mirrors the element API.

Networking

Web Native Gotcha
fetch('/api/x') (relative) absolute URL Native has no origin — relative paths fail. Use a configured base URL (EXPO_PUBLIC_API_URL).
CORS n/a No browser CORS, but you still need a reachable absolute host.
fetch, React Query, SWR same The libraries themselves work on native - see expo-data-fetching.
Next.js API routes Expo Router API routes Move server endpoints to Expo API routes on EAS Hosting - see eas-hosting.
Streaming responses (SSE, AI SDK useChat) expo/fetch with textStreaming RN's built-in fetch can't read a streaming response body. Use expo/fetch (streams, and works with the Vercel AI SDK) or an XHR-based polyfill.

Third-party services & SDKs

Browser SDKs don't run on native — each needs a native equivalent. The canonical, version-matched integrations live in the expo-examples skill (the expo/examples repo); reach for those instead of wiring from scratch.

Web service Native Gotcha
Stripe.js — digital goods / subscriptions store In-App Purchase via RevenueCat (react-native-purchases) Policy, not just an SDK swap. Apple & Google require IAP for in-app digital goods and take ~15–30%; shipping Stripe for them gets the app rejected. Decide at assess time — it can change the business model.
Stripe.js — physical goods / services @stripe/stripe-react-native (PaymentSheet, Apple/Google Pay) Stripe's RN SDK is allowed for physical goods; see expo-examples with-stripe.
Google Maps JS react-native-maps A native map view, not an embedded iframe.
Web Push expo-notifications (APNs / FCM) Different delivery + permission model.
OAuth redirect flow expo-auth-session (deep-link OAuth), or a native auth SDK (Clerk, Supabase…) No browser redirect — an app deep link completes the flow.
GA / Segment.js analytics native SDK (posthog-react-native, Segment RN) The web JS snippet won't load; swap for the native SDK.
<input type=file> / getUserMedia expo-image-picker / expo-camera / expo-document-picker Native pickers + runtime permissions.

Scrolling, keyboard & safe area

Web Native Gotcha
Body scrolls by default <ScrollView> / <FlatList> Nothing scrolls unless you make it. Wrap overflowing content explicitly.
overflow: scroll <ScrollView> Don't nest a ScrollView inside a FlatList of the same axis.
Keyboard overlaps inputs KeyboardAvoidingView The keyboard covers inputs by default; wrap forms to push content up.
Notch / status bar react-native-safe-area-context Content flows under the notch/home indicator unless you apply safe-area insets.
:focus ring, tab order manual No automatic focus management; wire ref/focus() and returnKeyType.

Source: SKILL.md on GitHub

No alerts23d3 checks · Risk SAFE
  • Gen Agent Trust Hub23d

    The skill provides a comprehensive guide for migrating web React applications to Expo. It uses standard development tools and commands for verification and feedback, fetching resources from trusted and well-known sources.

  • Socket23d

    No alerts

  • Snyk23d

    Risk: LOW · No issues

Signed by skilld at 5c8f62e. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 days ago.

Activeupdated 2 months ago
version
1.0.0

README badge

README badge for expo/skills/expo-web-to-native