Build.
Converting a Next.js app to iOS and Android with Capacitor
Step-by-step technical guide to export Next.js App Router projects, configure Capacitor native plugins, add biometric login, and pass Apple review.
When customers ask for a mobile app, rebuilding your existing Next.js web application from scratch in Swift and Kotlin is rarely practical. It doubles your codebase, requires hiring dedicated mobile developers, and slows down every future product update.
Capacitor bridges this gap by packaging your Next.js frontend code into native iOS and Android binaries. You maintain a single codebase, push updates across platforms, and distribute your app through the Apple App Store and Google Play Store.
However, packaging Next.js for mobile devices requires specific architectural adjustments. Here is the end-to-end technical sequence to configure your repository, add native capabilities, and pass Apple review.
1. Configure Next.js for static export
Capacitor requires static HTML, CSS, and client-side JavaScript assets bundled locally inside the native application shell. It cannot run a Node.js server runtime on an iPhone.
In your next.config.js or next.config.mjs, enable static exports and set the output directory:
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
distDir: 'out',
images: {
unoptimized: true, // Native shells require local unoptimized images
},
trailingSlash: true, // Guarantees clean directory-based routing on iOS
};
export default nextConfig;
Server components vs client components
Because mobile bundles run statically inside a webview, interactive screens that rely on browser events, authentication tokens, or client-side storage must include the 'use client' directive at the top of the file:
- Data fetching queries must execute against external API endpoints (such as Supabase, a backend Node API, or serverless functions) rather than internal Next.js Server Actions.
- Dynamic route segments should define
generateStaticParams()if rendered statically, or load data dynamically on the client after mount.
2. Initialize and configure Capacitor
Install the Capacitor core libraries and platform runtimes:
npm install @capacitor/core @capacitor/cli @capacitor/ios @capacitor/android
npx cap init "YourAppName" "com.yourcompany.appname" --web-dir="out"
In your root directory, inspect capacitor.config.ts. Ensure webDir points directly to your Next.js export folder (out):
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.yourcompany.appname',
appName: 'YourAppName',
webDir: 'out',
server: {
androidScheme: 'https',
iosScheme: 'https'
}
};
export default config;
Now add the native project scaffolds:
npm run build
npx cap add ios
npx cap add android
This creates native Xcode (ios/) and Android Studio (android/) projects that reference your compiled Next.js assets.
3. Handle notch, dynamic island, and safe area insets
Modern mobile screens feature notches, camera cutouts, dynamic islands, and bottom home indicators. Without safe area configuration, your Next.js header buttons and bottom navigation bars will hide behind device hardware.
First, update your root app/layout.tsx or index.html viewport meta tag to support edge-to-edge rendering:
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
Next, add CSS variables in your global stylesheet to handle padding:
:root {
--safe-top: env(safe-area-inset-top, 0px);
--safe-bottom: env(safe-area-inset-bottom, 0px);
--safe-left: env(safe-area-inset-left, 0px);
--safe-right: env(safe-area-inset-right, 0px);
}
/* Apply safe margins to fixed navigation bars */
.mobile-header {
padding-top: var(--safe-top);
}
.bottom-tab-bar {
padding-bottom: var(--safe-bottom);
}
This prevents tap targets from colliding with the status bar or the swipe indicator.
4. Satisfy Apple Guideline 4.2 (Minimum functionality)
The most common reason web apps get rejected during App Store review is Apple Guideline 4.2. Apple’s guidelines state that if your app does not provide functionality beyond what a website provides in a browser, it will not be approved.
To satisfy Guideline 4.2, integrate genuine native device features:
Native biometric authentication (Face ID & Touch ID)
Protect user data with native biometric verification rather than relying solely on browser passwords:
npm install @aparajita/capacitor-biometric-auth
import { BiometricAuth } from '@aparajita/capacitor-biometric-auth';
export async function authenticateWithBiometrics(): Promise<boolean> {
const isAvailable = await BiometricAuth.checkBiometry();
if (!isAvailable.isAvailable) return true;
const result = await BiometricAuth.authenticate({
reason: 'Confirm your identity to open your account',
cancelTitle: 'Cancel',
});
return result.authenticated;
}
Native haptic feedback
Add tactile feedback on critical user interactions (such as completing an order or toggling switches):
npm install @capacitor/haptics
import { Haptics, ImpactStyle } from '@capacitor/haptics';
export async function triggerHapticFeedback() {
await Haptics.impact({ style: ImpactStyle.Medium });
}
For real-world proof of this strategy in production, see our release of SlabMath on the Apple App Store. By packaging a Next.js web application with camera inputs, native haptic feedback, and offline calculation caching, the application sailed through Apple review without friction.
5. Account deletion compliance (Guideline 5.1.1(v))
Apple requires that every app offering user account creation must also offer user account deletion directly within the mobile app.
Providing an email link (“contact support to delete your account”) triggers an immediate rejection.
Implement an authenticated in-app flow that:
- Prompts the user with an explicit confirmation modal.
- Calls your backend API or Supabase Edge Function to purge user records, uploaded files, and relational data.
- Clears local device tokens and returns the app to the unauthenticated initial state.
6. The release build and TestFlight distribution
Before submitting to App Store Connect:
- Rebuild your Next.js assets:
npm run build. - Sync assets with the native iOS shell:
npx cap sync ios. - Open Xcode:
npx cap open ios. - In Xcode, configure your Apple Developer Team, Bundle Identifier, and App Icons.
- Archive the project (
Product -> Archive) and upload the build to TestFlight.
Test the build on real physical devices using TestFlight. Verify that the keyboard dismisses on tap, sessions remain active across app switching, and camera/biometric permissions trigger system dialogue prompts correctly.
If you want hands-on assistance packaging your web app or guiding it through Apple review, explore our web to mobile app service or request a free production diagnosis.