Your vibe-coded app has no View, so it cannot go mobile yet
Here is the test that ends most I will just turn my web app into an app plans: open the project your AI builder generated and search for View from React Native. You will not find one. You will find div, span, section — the DOM. Lovable, Bolt, and v0 are web builders by design. What they hand you is a website, and a website does not become an iOS app because you wrap it hard enough.
This trips up a specific person: the builder who shipped a working web MVP in a weekend, got users, and now needs to be in the App Store. They assume mobile is a deploy target — a checkbox, a wrapper, a one-time export. It is not. The gap between web output and what an app store accepts is structural, and no amount of prompting closes it.
A div is not a View, all the way down
A web app is HTML rendered by a browser engine. Layout is the CSS box model and the runtime is the DOM. A native app has none of that. On iOS a view is a UIView and on Android it is an android.view. React Native documents this directly: View is the most fundamental component for building a UI, a container supporting flexbox, style, touch handling, and accessibility, and it maps directly to the native view equivalent on whatever platform React Native is running on.
That mapping is the whole thesis. Views are designed to be nested with zero to many children and styled with StyleSheet for clarity and performance. There is no browser, no DOM, no div underneath when you run on a phone.
So when a web builder emits this:
// Web-only output: DOM elements, CSS classes, DOM events.
export function Dashboard({ onSave }: { onSave: () => void }) {
return (
<div className="flex flex-col gap-4 p-4">
<h1 className="text-2xl font-bold">Dashboard</h1>
<button onClick={onSave}>Save</button>
</div>
);
}Every token in it is web-only. Div and h1 are DOM elements. ClassName is a CSS class string. OnClick is a DOM event. The native equivalent shares none of those primitives:
// Native equivalent: platform views, StyleSheet, native gestures.
import { View, Text, Pressable, StyleSheet } from 'react-native';
export function Dashboard({ onSave }: { onSave: () => void }) {
return (
<View style={styles.column}>
<Text style={styles.title}>Dashboard</Text>
<Pressable onPress={onSave}>
<Text>Save</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
column: { flexDirection: 'column', gap: 16, padding: 16 },
title: { fontSize: 24, fontWeight: '700' },
});Different elements, different styling system, different event model, different text handling — bare strings cannot sit between tags in React Native, they must live inside Text. This is not a syntax difference you can sed through. It is two rendering targets that happen to both be written in React. That is why the export button you are looking for does not exist. There is nothing portable to export.
The converter market is a second tax
Because many teams hit this wall at once, a market appeared to sell a way over it. Paste your builder URL, get a native app. It is worth understanding what those tools actually do, because it is not what the marketing implies.
The honest version regenerates your web app as React Native in a second AI pass: every div becomes a View, every Tailwind class becomes a style object, every DOM event becomes a native handler. That gets you to a draft and leaves keyboard avoidance, safe areas, gesture handling, and navigation for you. You now maintain a second codebase that drifts from the first the moment you change either one.
The dishonest version wraps the website in a WKWebView shell. That sometimes passes review and produces a browser tab in a trench coat — no native navigation, no platform feel, janky scrolling, and real rejection risk where stores decline apps that are just a web page. Users feel it in three seconds.
Either way the converter is a tax. You paid one tool to generate web-only code, then pay a second tool to undo that decision. The cleaner move is to never make the web-only decision when mobile is on the roadmap.
Lovable itself frames its runtime as one credit balance for building, hosting, Cloud backend, and deployed-app AI features. That model is web-hosting-shaped. It does not produce platform views, signing pipelines, or store artefacts. Keep it for what it is good at — fast web validation — and do not ask it to be a native target.
One codebase. iOS, Android, and web.
The Fitness Kit ships with auth, a database, and a backend already connected — no setup. Live demo at fitness-preview.otf-kit.dev.
Start on components that already know both platforms
The reason a div cannot follow you to mobile is that the component is bound to one platform's primitives. Break that binding and the problem disappears: write against a component API that resolves to the right primitive on each platform.
That is the bet behind paired libraries like otfdashkit ui for web and ui-native for mobile. Same component name, same props, two implementations underneath:
// Web resolves to Radix plus Tailwind and the DOM.
import { Card, Button, Text } from '@otfdashkit/ui';
// Mobile resolves to native views on iOS and Android.
import { Card, Button, Text } from '@otfdashkit/ui-native';// The screen stays byte-for-byte the same in both targets.
export function DashboardScreen({ onSave }: { onSave: () => void }) {
return (
<Card>
<Text>Dashboard</Text>
<Button onPress={onSave}>Save</Button>
</Card>
);
}There is no div-to-View pass because you never wrote a div. Button knows it is a Pressable on native and a button element on web. Text is Text on native and a span on web. The translation happens once inside the tested library, not per project by a model that has never seen your app.
If you are planning that shared layer, read our one codebase three platforms breakdown for the repo shape, then the same component web mobile architecture piece for how one API resolves per target, and wire production crashes early with Sentry error tracking React Native production before your first TestFlight build.
What this enables in practice
When the component layer is platform-agnostic, go mobile stops being a migration and becomes a build target. One codebase, one design system, one set of components builds to iOS, Android, and web from the same source. No second repo to sync. Fix a bug once. A file-system agent extends both platforms at once because the component API it reads is the same one running on the phone.
# One source, three targets — no export step.
bun install
npx expo run:ios
npx expo run:android
npx expo export --platform webThat workflow also fixes the agent story. Cursor or Claude Code reading a cross-platform repo sees typed components, StyleSheet usage, and platform files side by side. Prompts land in the right file with the right primitive instead of asking a sandbox to imagine native from web output it cannot see.
If you already shipped web on a builder and face the store wall, converters can draft a starting point, but budget the maintenance headache honestly. The durable answer is to rebuild the UI layer on components that were cross-platform from line one — then the phone is just another target you already had.
Ready to skip the export trap? Browse kits with web plus native components your agent can extend at OTF templates — own the repo, ship to all three targets, and never regenerate the same screen twice.
Sources
- React Native Docs — View: most fundamental UI container; supports flexbox, style, touch, accessibility; maps directly to the native view equivalent (UIView, android.view); designed for nesting; use StyleSheet. https://reactnative.dev/docs/view
- Lovable Docs — Credits and usage: one credit balance for building, hosting, Cloud backend, and deployed-app AI features; background context that builder output is web-hosted. https://docs.lovable.dev/introduction/plans-and-credits
Stop wiring. Start shipping.
- Login, database, and backend already connected — nothing to set up
- iOS + Android + web from one codebase
- AI configs pre-tuned + 40+ tested prompts included