Web App to Mobile App with Capacitor: A Practical Guide (From Real Projects)
You already built the web app — now what?
Every year, clients ask the same question: “Can we turn this into a mobile app without rebuilding everything?”
The honest answer in 2026 is often yes, with Capacitor — if your web app is already React/Vite (or Vue/Angular) and your mobile requirements are standard: push notifications, offline storage, camera, share sheet, app store presence.
This guide documents the workflow I use across this site’s ecosystem — NetQuest (Vite submodule), Nepali Calendar, and freelance Capacitor deliveries — not theoretical tutorial content. For commercializing Capacitor boilerplates, see our separate licensing React Capacitor boilerplates analysis.
Capacitor vs alternatives (decision table)
| Approach | Code reuse | App store | Dev effort | Best when |
|---|---|---|---|---|
| PWA only | 100% web | No (Add to Home Screen) | Lowest | Internal tools, no store requirement |
| Capacitor | ~95% web | Yes | Medium | Existing web app, standard native APIs |
| React Native | ~60–80% logic | Yes | High | Mobile-first UX, native feel required |
| Flutter | 0% (Dart) | Yes | High | Greenfield, custom UI performance |
Capacitor’s niche: web developers shipping to app stores without learning Swift/Kotlin UI.
Our monorepo pattern (see BUILD_REFERENCE.md and GitHub Actions CI/CD post) builds Vite sub-apps into dist/netquest/, dist/nepali-calendar/, etc. Capacitor adds a parallel native shell pointing at that built web assets directory.
Project setup (Vite + React + Capacitor 6)
Starting from a working Vite app:
npm install @capacitor/core @capacitor/cli
npx cap init "App Name" com.yourcompany.appid
npm install @capacitor/android @capacitor/ios
Configure capacitor.config.ts:
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.arjankc.netquest',
appName: 'NetQuest',
webDir: 'dist',
server: {
// androidScheme: 'https' // recommended for secure context APIs
},
};
export default config;
Build web assets first, then sync to native projects:
npm run build
npx cap sync
npx cap open android # or ios
Critical rule: Always build → cap sync before testing native. Editing www/ manually gets overwritten.
Native plugins we actually use
| Plugin | Use case | Notes |
|---|---|---|
@capacitor/preferences |
Persist settings offline | Replaces localStorage edge cases on iOS |
@capacitor/push-notifications |
Re-engagement | Requires FCM + APNs setup |
@capacitor/share |
Share results/links | NetQuest score sharing pattern |
@capacitor/status-bar |
Brand color on launch | Small polish, big perceived quality |
@capacitor/splash-screen |
Launch experience | Keep duration short |
@capacitor/app |
Back button + deep links | Android hardware back handling |
Install per plugin, then npx cap sync. Do not bundle plugins you do not call — each adds native dependency surface.
Lessons from NetQuest and Nepali Calendar
1. Treat mobile as a viewport, not a separate product
Both apps share web codebase with responsive breakpoints. Capacitor does not remove the need for mobile UX:
- Touch targets ≥ 44px
- Bottom navigation for thumb reach
- Avoid hover-only interactions
- Test on mid-range Android (not just flagship iPhone)
2. Offline-first for utility apps
Calendar and quiz apps get opened in low-connectivity contexts (trekking areas, commute). Cache static data locally:
- Ship JSON assets in bundle for calendar meta
- Use
@capacitor/preferencesfor user settings - Show stale-data indicator rather than blank screen
3. App Store review surprises
Common rejection reasons we have hit or seen:
| Issue | Prevention |
|---|---|
| Broken login on review device | Provide demo account in review notes |
| Missing privacy policy URL | Host policy on HTTPS page |
| WebView wrapper with minimal value | Add push, offline, or native navigation |
| Placeholder content | Production-ready assets before submission |
| IAP without StoreKit/RevenueCat | Use official billing APIs |
Build in 2–3 weeks of review buffer for first submission.
4. Deep linking and URL schemes
Web routes (/quiz/, /calendar/2082/) should map to app routes. Configure:
- Android App Links (assetlinks.json)
- iOS Universal Links (apple-app-site-association)
- Custom URL scheme fallback
Our static site hosts association files at well-known paths — same pattern as Cloudflare routing.
Development workflow
┌─────────────┐ npm run build ┌──────────┐
│ Vite/React │ ─────────────────────► │ dist/ │
│ source │ └────┬─────┘
└─────────────┘ │ npx cap sync
▼
┌─────────────────────────┐
│ android/ ios/ projects │
│ (Android Studio / Xcode)│
└─────────────────────────┘
Daily loop:
- Develop in browser (
npm run dev) — fastest iteration - Build + sync before native-specific testing
- Test on real device weekly (simulators miss WebView quirks)
- CI: separate job for
cap sync+ gradle assemble (Android) — iOS needs macOS runner
This site’s npm run build:full includes Vite submodules; mobile builds typically run a scoped submodule build first.
Performance expectations
Capacitor apps are WebView-based. Expect:
- Startup: 1–3s cold start depending on bundle size
- Animations: 60fps achievable with CSS transforms; heavy JS main thread blocks UI
- Memory: Large DOM hurts more than on desktop Chrome
Mitigations that worked for us:
- Code-split routes (Vite dynamic import)
- Tree-shake unused icon libraries
- Avoid massive chart libraries on mobile views
- Use
will-changesparingly for animations
Compare against Core Web Vitals patterns — same LCP/INP thinking applies inside WebView.
OTA updates without app store delay
Capacitor allows live updates to web assets (not native code changes) via:
-
@capacitor/live-update(Ionic Appflow) - Custom OTA server (self-hosted bundle zip)
Caution: Apple restricts OTA that materially changes app purpose. Bug fixes and content updates are generally fine; turning a calendar app into a gambling app via OTA is not.
For Nepal-based teams, self-hosted OTA on Cloudflare R2 + worker is cost-effective vs Appflow subscription.
Security checklist
- HTTPS only (no mixed content)
- Certificate pinning for high-security apps (optional, adds complexity)
-
Secure storage for tokens (
@capacitor/preferencesvs plain localStorage) - Obfuscate API keys — nothing secret in client bundle
- Validate deep link parameters server-side
Cost model (realistic)
| Line item | One-time | Recurring |
|---|---|---|
| Apple Developer Program | — | $99/year |
| Google Play Console | $25 | — |
| Push (FCM free, APNs via Apple) | — | — |
| macOS hardware (iOS builds) | $800+ | — |
| CI macOS minutes | — | Variable |
| Developer time | 40–120 hrs first ship | 5–15 hrs/release |
Capacitor saves hundreds of hours vs native rewrite — but app store logistics still cost real money and time.
When to graduate off Capacitor
Signals to migrate toward React Native or native:
- App Store feedback cites “web app feel”
- Performance bottlenecks in WebView profiling
- Need for complex background tasks, Bluetooth, AR
- Native UI components required by platform HIG
Most business utility apps never hit this ceiling.


