Next.js + Capacitor walkthrough
From zero to your first OTA update. This guide creates a Next.js app, adds Capacitor, installs the OtaKit plugin, and ships an update — all in about 10 minutes.
Create the Next.js app
npx create-next-app@latest my-app cd my-app
Add Capacitor
npm install @capacitor/core @capacitor/cli npx cap init my-app com.example.myapp
Edit capacitor.config.ts and set webDir to your build output directory. For Next.js static export, use "out":
// capacitor.config.ts
import type { CapacitorConfig } from "@capacitor/cli";
const config: CapacitorConfig = {
appId: "com.example.myapp",
appName: "my-app",
webDir: "out",
};
export default config;Configure Next.js for static export:
// next.config.ts
const nextConfig = {
output: "export",
};
export default nextConfig;Add native platforms
npm install @capacitor/ios @capacitor/android npx cap add ios npx cap add android
Install the OtaKit plugin
npm install @otakit/capacitor-updater npx cap sync
Create an OtaKit account and app
Sign up at the OtaKit dashboard, create an organization and app, then install the CLI and sign in:
npm install -g @otakit/cli otakit login
Copy the OtaKit appId from the dashboard. The CLI will read your bundle output directory from capacitor.config.ts, so no separate OtaKit project file is required.
Configure the plugin
Add the OtaKit config to capacitor.config.ts using the appId from the dashboard:
// capacitor.config.ts
const config: CapacitorConfig = {
appId: "com.example.myapp",
appName: "my-app",
webDir: "out",
plugins: {
OtaKit: {
appId: "YOUR_OTAKIT_APP_ID",
appReadyTimeout: 10000,
// Optional:
// updateMode: "manual",
// updateMode: "immediate",
}
}
};Optional advanced path: otakit register --slug com.example.myapp can create the app from the CLI, but the normal hosted flow starts in the dashboard.
Add notifyAppReady()
Create a client component that calls notifyAppReady() when the app loads. This tells the plugin the bundle is healthy. If it's not called within the timeout, the plugin rolls back.
// app/components/AppReady.tsx
"use client";
import { useEffect } from "react";
import { OtaKit } from "@otakit/capacitor-updater";
import { Capacitor } from "@capacitor/core";
export function AppReady() {
useEffect(() => {
if (Capacitor.isNativePlatform()) {
OtaKit.notifyAppReady();
}
}, []);
return null;
}Add it to your root layout:
// app/layout.tsx
import { AppReady } from "./components/AppReady";
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<AppReady />
{children}
</body>
</html>
);
}Build and run on a device
npm run build npx cap sync npx cap run ios # or: npx cap run android
Verify the app launches. Check the Xcode / Android Studio console for OtaKit log messages confirming it checked for updates.
Ship your first OTA update
Make a visible change to your app — for example, update the heading in app/page.tsx. Then build and release:
# Build and ship npm run build otakit upload --release
Now relaunch the app on your device. It will detect the new version, download it, and apply it on the next cold launch by default.
That's it — your first OTA update is live. No app store submission, no review wait.
What's next?
- Start with the base stream, then add
staging/productionwhen needed. - Set up CI automation with GitHub Actions.
- See the CLI reference for release history and more.