Turn any Vite web app into iOS and Android apps with Capacitor
A framework-agnostic guide: take any Vite-built web app to native iOS and Android with Capacitor, then push over-the-air updates with OtaKit. Works with vanilla JS, Solid, Preact, and more.
Capacitor doesn't care which framework you use — it ships whatever static files your build produces. If your app is built with Vite, this guide applies whether you're on React, Vue, Solid, Preact, Lit, or plain vanilla JavaScript. Same steps, same result: native iOS and Android apps from your existing web build.
We'll go from a Vite project to installable native apps, then add OtaKit for over-the-air updates.
Mental model: Capacitor targets your dist/ folder, not your framework. If it builds to static files, it ships.
1. Set a relative base
Native web views load from a file:// origin, so absolute asset paths break. Tell Vite to emit relative paths:
// vite.config.ts
import { defineConfig } from "vite";
export default defineConfig({
base: "./",
});Running npm run build produces a static site in dist/ — that's your payload.
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. Create and run the native projects
npm run build npx cap add ios npx cap add android npx cap sync
// package.json
"scripts": {
"mobile": "vite build && npx cap sync"
}Then npx cap open ios / npx cap open android to run on a simulator or device.
4. Reach device features
Any native capability is a Capacitor plugin you import like a normal dependency:
npm install @capacitor/preferences npx cap sync
import { Preferences } from "@capacitor/preferences";
await Preferences.set({ key: "onboarded", value: "true" });
const { value } = await Preferences.get({ key: "onboarded" });5. Add live updates with OtaKit
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" },
},
};Call notifyAppReady() after your app initializes — the exact spot depends on your framework, but the idea is the same everywhere: signal a clean boot so OtaKit can auto-roll-back a broken release.
import { Capacitor } from "@capacitor/core";
import { OtaKit } from "@otakit/capacitor-updater";
if (Capacitor.isNativePlatform()) {
OtaKit.notifyAppReady();
}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. Native changes still require a store submission; your Vite bundle ships over the air — signed, hash-verified, CDN-delivered, and with no per-user or bandwidth billing.
On React or Vue specifically? The React and Vue guides show the framework-exact placement of notifyAppReady().
Where to go next
Read how OTA works, then reduce your bundle size for faster startups and smaller updates.