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.

1

Create the Next.js app

npx create-next-app@latest my-app
cd my-app
2

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;
3

Add native platforms

npm install @capacitor/ios @capacitor/android
npx cap add ios
npx cap add android
4

Install the OtaKit plugin

npm install @otakit/capacitor-updater
npx cap sync
5

Create an OtaKit account and register your app

Sign up at the OtaKit dashboard, create an organization, and copy your API key from the Organization tab. Then:

npm install -g @otakit/cli

export OTAKIT_SECRET_KEY=otakit_sk_...
export OTAKIT_SERVER_URL=https://your-domain.com

otakit register --slug com.example.myapp

Copy the returned appId and publicKey into your plugin config, then export:

export OTAKIT_APP_ID=your-app-id
6

Configure the plugin

Add the Updater config to capacitor.config.ts using the values returned by otakit register:

// capacitor.config.ts
const config: CapacitorConfig = {
  appId: "com.example.myapp",
  appName: "my-app",
  webDir: "out",
  plugins: {
    Updater: {
      appId: "YOUR_APP_ID",
      publicKey: "YOUR_PUBLIC_KEY",
      defaultChannel: "default",
      updateMode: "silent",
      autoUpdate: true,
      appReadyTimeout: 10000
    }
  }
};
7

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 { Updater } from "@otakit/capacitor-updater";
import { Capacitor } from "@capacitor/core";

export function AppReady() {
  useEffect(() => {
    if (Capacitor.isNativePlatform()) {
      Updater.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>
  );
}
8

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.

9

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:

# Bump version in package.json (e.g. 0.1.0 → 0.1.1)
npm version patch

# Build and ship
npm run build
otakit upload
otakit release

Now relaunch the app on your device. It will detect the new version, download it, and apply it on the next restart.

That's it — your first OTA update is live. No app store submission, no review wait.

What's next?