Build iOS and Android apps from your Vue app with Capacitor
Convert a Vue 3 (Vite) web app into native iOS and Android apps with Capacitor, add device plugins, and wire up OtaKit live updates to push web-layer changes over the air in minutes.
Your Vue app can become a real iOS and Android app without a rewrite. Capacitor wraps the static build your Vue project already produces in a native shell, exposes device APIs through plugins, and submits to both stores like any native app. Your components, composables, Pinia stores, and router all run unchanged.
This guide takes a Vue 3 + Vite app to installable native builds, then adds OtaKit so you can ship web-layer updates over the air in minutes instead of queuing behind a store review.
Mental model: Capacitor gives your existing Vue app a native container. OtaKit keeps that web layer current after it ships.
Prerequisites
- A Vue 3 app built with Vite.
- Node 20+, Xcode for iOS, Android Studio for Android.
1. Prepare the Vite build
Vite outputs a static site to dist/ on npm run build — that's what Capacitor ships. Set a relative base so assets resolve from the native file:// origin:
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
base: "./",
plugins: [vue()],
});If you use Vue Router, prefer hash history (createWebHashHistory) for the native build — it avoids deep-link 404s inside the web view without extra native routing config.
2. Add Capacitor
npm install @capacitor/core npm install -D @capacitor/cli npx cap init "My App" com.example.myapp --web-dir dist npm install @capacitor/ios @capacitor/android
// capacitor.config.ts
import type { CapacitorConfig } from "@capacitor/cli";
const config: CapacitorConfig = {
appId: "com.example.myapp",
appName: "My App",
webDir: "dist",
};
export default config;3. Build the native projects
npm run build npx cap add ios npx cap add android npx cap sync
Run with npx cap open ios or npx cap open android. Add a script to keep the build-and-sync loop one command:
// package.json
"scripts": {
"mobile": "vite build && npx cap sync"
}4. Use native device features
Device APIs are plugins you import inside a composable or component:
npm install @capacitor/geolocation npx cap sync
import { Geolocation } from "@capacitor/geolocation";
async function currentPosition() {
const { coords } = await Geolocation.getCurrentPosition();
return { lat: coords.latitude, lng: coords.longitude };
}5. Add live updates with OtaKit
Install the plugin and set your OtaKit app id:
npm install @otakit/capacitor-updater npx cap sync
// capacitor.config.ts
const config: CapacitorConfig = {
appId: "com.example.myapp",
appName: "My App",
webDir: "dist",
plugins: {
OtaKit: { appId: "YOUR_OTAKIT_APP_ID" },
},
};Signal a successful boot from your root component so OtaKit can auto-roll-back a bad release:
import { onMounted } from "vue";
import { Capacitor } from "@capacitor/core";
import { OtaKit } from "@otakit/capacitor-updater";
onMounted(() => {
if (Capacitor.isNativePlatform()) OtaKit.notifyAppReady();
});Then release:
npm install -g @otakit/cli otakit login npm run build otakit upload --release
Once your app is on the stores with the plugin configured, each otakit upload --release reaches installed devices on the next launch. Bundles are ES256-signed, hash-verified, CDN-delivered, and optionally end-to-end encrypted — with no per-user or bandwidth billing. Native changes still go through the store; your Vue bundle ships over the air.
New to how any of this works under the hood? Start with how OTA updates work in Capacitor apps.
Where to go next
The setup guide gets you running end to end. Then look at common OTA mistakes to avoid and automating releases with GitHub Actions.