Turn your React app into iOS and Android apps with Capacitor
A step-by-step guide to wrapping a plain React (Vite) app as native iOS and Android with Capacitor — no React Native rewrite — plus over-the-air updates with OtaKit so you can ship fixes without a store review.
The fastest way to get a React app onto the App Store and Google Play isn't React Native — it's Capacitor. Capacitor wraps your existing React build in a native shell, so the components, hooks, and libraries you already have run as-is. No new framework, no parallel codebase, no rewriting your UI in native primitives.
This guide takes a plain React app (built with Vite) to installable iOS and Android binaries, then adds OtaKit so you can push updates to the web layer over the air instead of shipping a new build for every fix.
Mental model: React Native rewrites your UI into native views. Capacitor keeps your web UI and gives it a native container. For a full breakdown of the tradeoff, see React Native vs Capacitor.
Prerequisites
- A React app built with Vite (or any bundler that outputs static files).
- Node 20+, Xcode for iOS builds, Android Studio for Android builds.
1. Confirm your build output
Capacitor ships static assets, so you just need a folder of built files. A Vite React app already produces one at dist/ when you run npm run build. If you're on Create React App, that folder is build/ instead — note whichever it is; Capacitor needs to know.
One thing to check: use relative asset paths. In vite.config.ts, set base: './' so assets resolve correctly from the native file:// context.
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
Your config should point at the Vite output directory:
// 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 apps
npm run build npx cap add ios npx cap add android npx cap sync
Open either platform with npx cap open ios / npx cap open android and run it. Wrap the repetitive part in a script so you never forget the sync step:
// package.json
"scripts": {
"mobile": "vite build && npx cap sync"
}4. Reach device features
Anything native is a Capacitor plugin you import like any React dependency. For example, the camera:
npm install @capacitor/camera npx cap sync
import { Camera, CameraResultType } from "@capacitor/camera";
async function takePhoto() {
const photo = await Camera.getPhoto({
quality: 90,
resultType: CameraResultType.Uri,
});
return photo.webPath;
}On the web the same call falls back to a file input, so your components stay portable across browser and device.
5. Add over-the-air updates with OtaKit
Because your UI is the web layer, you can update it without a store round-trip. Install the plugin and configure 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" },
},
};Call notifyAppReady() after your app mounts so OtaKit knows the new bundle booted successfully — if it never hears back, it rolls the device back automatically:
import { useEffect } from "react";
import { Capacitor } from "@capacitor/core";
import { OtaKit } from "@otakit/capacitor-updater";
function App() {
useEffect(() => {
if (Capacitor.isNativePlatform()) OtaKit.notifyAppReady();
}, []);
// ...
}Install the CLI, then ship:
npm install -g @otakit/cli otakit login npm run build otakit upload --release
After your first store release with the plugin in place, every otakit upload --release lands on installed devices on their next launch. Bundles are signed and verified by SHA-256, and download directly from a CDN edge — with no monthly-active-user or bandwidth metering. Native changes still require a store submission; your React bundle does not.
Want to roll out to a slice of users first? See staged rollouts for the channel-based approach.
Where to go next
The React guide has the complete setup. From there, automate releases in CI and read up on securing your update pipeline.