Build iOS and Android apps from your SvelteKit app with Capacitor
Wrap a SvelteKit app as native iOS and Android with Capacitor using the static adapter, then wire up OtaKit so you can ship JS and CSS changes over the air in minutes.
SvelteKit apps are fast, small, and — with the static adapter — a perfect payload for Capacitor. You keep your routes, components, and stores, and ship the same codebase to the web, iOS, and Android. No rewrite, no separate mobile stack.
This guide takes a SvelteKit app to installable native builds, then adds OtaKit so you can push updates over the air rather than through a store review each time.
Mental model: the static adapter turns SvelteKit into a shippable web bundle; Capacitor wraps it natively; OtaKit keeps it updated after launch.
1. Switch to the static adapter
Install and configure adapter-static so SvelteKit prerenders a fully static site:
npm install -D @sveltejs/adapter-static
// svelte.config.js
import adapter from "@sveltejs/adapter-static";
export default {
kit: {
adapter: adapter({ fallback: "index.html" }),
paths: { relative: true }, // relative paths for file:// context
},
};Enable prerendering in your root layout. The index.html fallback lets client-side routing handle any path inside the web view:
// src/routes/+layout.ts export const prerender = true; export const ssr = false;
Server endpoints (+server.ts) don't run on-device — call your API over HTTPS instead. Building now emits a static site to build/.
2. Add Capacitor
npm install @capacitor/core npm install -D @capacitor/cli npx cap init "My App" com.example.myapp --web-dir build 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: "build",
};
export default config;3. Build the native apps
npm run build npx cap add ios npx cap add android npx cap sync
// package.json
"scripts": {
"mobile": "vite build && npx cap sync"
}4. Reach device features
npm install @capacitor/haptics npx cap sync
import { Haptics, ImpactStyle } from "@capacitor/haptics";
async function tap() {
await Haptics.impact({ style: ImpactStyle.Light });
}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: "build",
plugins: {
OtaKit: { appId: "YOUR_OTAKIT_APP_ID" },
},
};Confirm a successful boot from your root layout so OtaKit can auto-roll-back:
// src/routes/+layout.svelte
<script lang="ts">
import { onMount } from "svelte";
import { Capacitor } from "@capacitor/core";
import { OtaKit } from "@otakit/capacitor-updater";
onMount(() => {
if (Capacitor.isNativePlatform()) OtaKit.notifyAppReady();
});
</script>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 the next launch. Bundles are signed, hash-verified, and CDN-delivered — with no monthly-active-user or bandwidth metering. Native changes still go through the store; your SvelteKit build ships over the air.
Want to roll out gradually? See staged rollouts for the channel-based approach.
Where to go next
The setup guide has the complete flow. Then read common OTA mistakes and OTA security.