Phase 1
Prepare the web app
Get the code on your machine and native-ready
Capacitor bundles the finished output of your web build. Anything that only works on a live server — server-rendered routes, server functions, redirects to your own domain — needs a plan before you wrap. Fix this now; debugging it inside a native shell is far harder.
Connect the project to GitHub from Lovable (top-right GitHub button → Connect), then clone it. Everything from here happens in your own repository — the native folders and signing configuration cannot be created from inside Lovable.
git clone https://github.com/YOUR-USER/my-app.git
cd my-app
npm install
npm run dev # confirm it works at http://localhost:8080The native shell loads files from disk, not from a Node server. Your app must build to a folder of static assets that works when opened from the filesystem with client-side routing.
- Run
npm run buildand confirm it completes without errors. - Note the output folder — usually
dist/ordist/client/. That path becomeswebDirin your Capacitor config. - Serve the output locally and click through every route:
npx serve dist(or your output folder). - Check the browser console for 404s on assets. Absolute paths that assume a domain root are the usual culprit.
// src/lib/api-base.ts
export const API_BASE =
import.meta.env.VITE_API_BASE ?? "https://your-app.lovable.app";
export const api = (path: string) => `${API_BASE}${path}`;| Pattern | What happens natively | Fix |
|---|---|---|
Relative fetch (/api/x) | Resolves against capacitor://localhost and fails | Prefix with your absolute API base URL |
| Cookies for auth | Third-party cookie rules block them in the web view | Use bearer tokens in native-safe storage |
window.open to external sites | Opens inside your app or is blocked | Use the Browser plugin to open the system browser |
| OAuth redirect to a web callback | Redirect never returns to the app | Use a custom scheme / App Link callback |
localStorage for critical data | Can be evicted by the OS | Use the Preferences plugin |
| Hover-only interactions | No hover on touch devices | Give every hover action a tap equivalent |
Fixed 100vh layouts | Clipped by status bar and home indicator | Use 100dvh plus safe-area insets |
Downloads via <a download> | Silently does nothing | Use the Filesystem + Share plugins |
Test at 360×640 (small Android) and 390×844 (iPhone) in your browser's device mode before you wrap. Tap targets should be at least 44×44 px, text at least 16 px in inputs (smaller text makes iOS zoom on focus).
<meta
name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover"
/>:root {
--safe-top: env(safe-area-inset-top, 0px);
--safe-bottom: env(safe-area-inset-bottom, 0px);
}
.app-shell {
min-height: 100dvh;
padding-top: var(--safe-top);
padding-bottom: var(--safe-bottom);
}Both stores require a publicly reachable privacy policy URL before you can submit — even for an app that collects nothing. Publish it as a page on your web app now so the URL is stable.
- What data you collect (accounts, analytics, crash logs, device identifiers).
- Why you collect it and who you share it with (name each third party).
- How long you keep it and how a user requests deletion.
- A contact email that you actually monitor.
- If your app has accounts, Play also requires an in-app account deletion path and a public web URL for deletion requests.