Pendahuluan
Ingin membangun aplikasi mobile dengan Next.js dari nol? Panduan ini memandu Anda membuat proyek Next.js 15 baru yang dikonfigurasi untuk mobile sejak hari pertama, lalu mengemasnya menjadi aplikasi native iOS dan Android menggunakan Capacitor 8.
Di akhir tutorial ini, Anda akan punya aplikasi mobile yang berjalan di simulator, yang bisa terus Anda kembangkan dan akhirnya publikasikan ke App Store dan Google Play.
Waktu yang dibutuhkan: ~30 menit
Apa yang akan Anda build:
- Proyek Next.js 15 baru dengan App Router
- Konfigurasi static export untuk mobile
- Capacitor 8 dengan plugin esensial
- Aplikasi native iOS dan Android
- Setup development live reload
Sudah punya aplikasi Next.js? Cek Ubah Aplikasi Next.js Anda ke Mobile instead.
Prerequisites
Pastikan Anda sudah menginstal:
- Node.js 18+ (cek dengan
node --version) - Bun package manager ("
curl -fsSL https://bun.sh/install | bash) - Xcode (khusus macOS, untuk pengembangan iOS)
- Android Studio (untuk pengembangan Android)
Langkah 1: Buat Proyek Next.js Baru
Mulai dengan membuat proyek Next.js 15 baru:
bunx create-next-app@latest my-mobile-app
Saat muncul prompt, pilih opsi berikut:
- TypeScript: Ya (direkomendasikan)
- ESLint: Ya
- Tailwind CSS: Ya (direkomendasikan untuk styling mobile)
src/direktori: Ya- App Router: Ya (direkomendasikan)
- Alias impor: Default (
@/*)
Navigasi ke proyek Anda:
cd my-mobile-app
Langkah 2: Konfigurasi Next.js untuk Static Export
Capacitor memerlukan file statis HTML/JS/CSS. Konfigurasi Next.js untuk static export dengan memperbarui next.config.ts:
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
output: 'export',
images: {
unoptimized: true,
},
// Ensure trailing slashes for proper routing in Capacitor
trailingSlash: true,
};
export default nextConfig;
Mengapa pengaturan ini?
output: 'export'— Menghasilkan HTML statis alih-alih membutuhkan server Node.jsimages: { unoptimized: true }— Menonaktifkan Next.js Image Optimization (butuh server)trailingSlash: true— Memastikan routing berjalan lancar di native WebView
Langkah 3: Tambahkan Mobile Scripts
Perbarui file package.json dengan script pengembangan mobile:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"mobile": "bun run build && bunx cap sync",
"mobile:ios": "bun run mobile && bunx cap open ios",
"mobile:android": "bun run mobile && bunx cap open android"
}
}
Tes hasil build:
bun run build
Anda akan melihat folder out direktori berisi file statis Anda.
Langkah 4: Instal Capacitor 8
Instal paket inti Capacitor:
bun add @capacitor/core
bun add -D @capacitor/cli
Instal plugin esensial yang dibutuhkan kebanyakan aplikasi mobile:
bun add @capacitor/app @capacitor/keyboard @capacitor/splash-screen @capacitor/status-bar @capacitor/preferences
Kegunaan plugin ini:
- @capacitor/app — Event lifecycle App (foreground/background, deep links)
- @capacitor/keyboard — Kontrol perilaku keyboard
- @capacitor/splash-screen — Kontrol native splash screen
- @capacitor/status-bar — Styling status bar perangkat
- @capacitor/preferences — Penyimpanan key-value (seperti localStorage tapi native)
Langkah 5: Inisialisasi Capacitor
Inisialisasi Capacitor dengan detail proyek Anda:
bunx cap init "My Mobile App" com.example.mymobileapp --web-dir out
Ganti:
"My Mobile App"dengan nama tampilan aplikasi Andacom.example.mymobileappdengan app ID Anda (notasi reverse domain)
Ini akan membuat capacitor.config.ts. Perbarui dengan konfigurasi plugin:
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.mymobileapp',
appName: 'My Mobile App',
webDir: 'out',
plugins: {
SplashScreen: {
launchShowDuration: 2000,
launchAutoHide: true,
androidScaleType: 'CENTER_CROP',
splashFullScreen: true,
splashImmersive: true,
},
Keyboard: {
resize: 'body',
resizeOnFullScreen: true,
},
StatusBar: {
style: 'light',
},
},
};
export default config;
Langkah 6: Tambahkan Platform Native
Instal paket platform:
bun add @capacitor/ios @capacitor/android
Generate proyek native:
bunx cap add ios
bunx cap add android
Ini akan membuat ios dan android direktori yang berisi proyek native.
Langkah 7: Build dan Run
Build proyek Anda dan sinkronkan dengan platform native:
bun run mobile
Buka di iOS Simulator:
bun run mobile:ios
Atau Android Emulator:
bun run mobile:android
Di Xcode (iOS):
- Pilih simulator dari dropdown perangkat
- Klik tombol Play atau tekan
Cmd + R
Di Android Studio:
- Tunggu Gradle selesai sinkronisasi
- Pilih emulator dari dropdown perangkat
- Klik tombol Run atau tekan
Shift + F10
Langkah 8: Atur Live Reload
Untuk pengembangan lebih cepat, aktifkan live reload agar perubahan langsung muncul di perangkat Anda.
- Temukan alamat IP lokal Anda:
# macOS
ipconfig getifaddr en0
# Windows
ipconfig
- Buat konfigurasi Capacitor untuk development. Tambahkan ke
capacitor.config.ts:
import type { CapacitorConfig } from '@capacitor/cli';
const devConfig: CapacitorConfig = {
appId: 'com.example.mymobileapp',
appName: 'My Mobile App',
webDir: 'out',
server: {
url: 'http://YOUR_IP_ADDRESS:3000',
cleartext: true,
},
plugins: {
// ... same plugin config
},
};
const prodConfig: CapacitorConfig = {
appId: 'com.example.mymobileapp',
appName: 'My Mobile App',
webDir: 'out',
plugins: {
// ... same plugin config
},
};
const config = process.env.NODE_ENV === 'development' ? devConfig : prodConfig;
export default config;
- Jalankan dev server dan salin konfigurasi ke native:
bun run dev &
NODE_ENV=development bunx cap copy
- Rebuild di Xcode/Android Studio
Sekarang, setiap perubahan pada code Next.js Anda akan hot-reload di perangkat.
Langkah 9: Buat Layar Mobile Pertama Anda
Mari buat home screen sederhana yang mobile-friendly. Update src/app/page.tsx:
'use client';
import { useEffect, useState } from 'react';
import { App } from '@capacitor/app';
import { Keyboard } from '@capacitor/keyboard';
export default function Home() {
const [appInfo, setAppInfo] = useState<{ name: string; version: string } | null>(null);
useEffect(() => {
// Get app info on mount
App.getInfo().then(setAppInfo).catch(console.error);
// Handle back button on Android
const backHandler = App.addListener('backButton', ({ canGoBack }) => {
if (!canGoBack) {
App.exitApp();
} else {
window.history.back();
}
});
// Hide keyboard when tapping outside inputs
const keyboardHandler = Keyboard.addListener('keyboardWillShow', () => {
document.body.classList.add('keyboard-open');
});
return () => {
backHandler.then(h => h.remove());
keyboardHandler.then(h => h.remove());
};
}, []);
return (
<main className="min-h-screen bg-linear-to-b from-blue-500 to-blue-700 flex flex-col items-center justify-center p-6 text-white">
<h1 className="text-4xl font-bold mb-4">My Mobile App</h1>
<p className="text-xl mb-8 text-center opacity-90">
Built with Next.js 15 + Capacitor 8
</p>
{appInfo && (
<div className="bg-white/20 rounded-lg p-4 backdrop-blur-sm">
<p className="text-sm">
{appInfo.name} v{appInfo.version}
</p>
</div>
)}
<div className="mt-12 space-y-4 w-full max-w-sm">
<button className="w-full py-4 px-6 bg-white text-blue-600 rounded-xl font-semibold text-lg shadow-lg active:scale-95 transition-transform">
Get Started
</button>
<button className="w-full py-4 px-6 bg-white/20 text-white rounded-xl font-semibold text-lg backdrop-blur-sm active:scale-95 transition-transform">
Learn More
</button>
</div>
</main>
);
}
Langkah 10: Tambahkan Safe Area Handling
Perangkat mobile memiliki notch, home indicator, dan status bar. Tambahkan safe area handling menggunakan Tailwind.
Update src/app/globals.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--sat: env(safe-area-inset-top);
--sar: env(safe-area-inset-right);
--sab: env(safe-area-inset-bottom);
--sal: env(safe-area-inset-left);
}
body {
padding-top: var(--sat);
padding-right: var(--sar);
padding-bottom: var(--sab);
padding-left: var(--sal);
}
/* Prevent text selection on mobile */
* {
-webkit-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
/* Allow text selection in inputs */
input, textarea {
-webkit-user-select: auto;
user-select: auto;
}
/* Keyboard handling */
.keyboard-open {
--sab: 0px;
}
Struktur Proyek
Struktur proyek Anda sekarang akan terlihat seperti ini:
my-mobile-app/
├── android/ # Android native project
├── ios/ # iOS native project
├── out/ # Static build output
├── src/
│ ├── app/
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ └── page.tsx
│ └── ...
├── capacitor.config.ts # Capacitor configuration
├── next.config.ts # Next.js configuration
├── package.json
└── ...
Langkah Berikutnya
Sekarang Anda sudah memiliki aplikasi mobile Next.js yang berjalan. Berikut langkah selanjutnya:
Setup Esensial
- Ikon Aplikasi: Ganti ikon default di
ios/App/App/Assets.xcassetsdanandroid/app/src/main/res - Splash Screen: Kustomisasi di proyek native atau gunakan
@capacitor/splash-screenkonfigurasi - Deep Links: Konfigurasi URL scheme untuk aplikasi Anda
Tambah Fitur Lainnya
- Kamera:
bun add @capacitor/camera - Geolocation:
bun add @capacitor/geolocation - Push Notifications:
bun add @capacitor/push-notifications - File System:
bun add @capacitor/filesystem
UI dan transisi native
Gunakan plugin Capgo alih-alih Konsta UI agar terasa lebih native:
- @capgo/capacitor-native-navigation — Tab bar Liquid Glass dan navbar native
- @capgo/capacitor-transitions — transisi halaman yang terasa native
bun add @capgo/capacitor-native-navigation @capgo/capacitor-transitions
bunx cap sync
Untuk safe areas Tailwind, tambahkan @capgo/tailwind-capacitor:
bun add -D tailwind-capacitor
Lihat Menggunakan @capgo/capacitor-native-navigation, Menggunakan @capgo/capacitor-transitions, dan repo tailwind-capacitor untuk setup khusus Next.js.
Memperbaiki Masalah Layout iOS (Viewport, Safe Area, dan Horizontal Overflow)
Jika konten terlihat terpotong, bergeser, atau bisa di-scroll secara horizontal di iOS, menambahkan lebih banyak overflow-x: hidden atau hanya mengutak-atik tag viewport saja biasanya tidak memperbaikinya. Lakukan pemeriksaan ini secara berurutan.
Pastikan meta tag viewport diterapkan dengan benar
App Router (app/): export viewport dari app/layout.tsx:
import type { Viewport } from 'next';
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
viewportFit: 'cover',
};
Pages Router (pages/): masukkan viewport meta tag ke dalam pages/_app.tsx, bukan _document.tsx.
Tangani iOS safe area hanya dari satu root wrapper
Buat satu app shell dan terapkan safe area padding di sana — bukan di beberapa komponen bersarang:
html,
body,
#__next {
width: 100%;
min-height: 100%;
margin: 0;
padding: 0;
overflow-x: hidden;
}
* {
box-sizing: border-box;
}
.app-shell {
min-height: 100dvh;
width: 100%;
padding-top: env(safe-area-inset-top);
padding-right: env(safe-area-inset-right);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
}
Bungkus seluruh konten halaman di dalam .app-shell. Padding safe-area yang dobel di header, modal, dan wrapper layout sering membuat UI terlihat terpotong atau terlalu besar.
Dengan @capgo/tailwind-capacitor, Anda bisa menggunakan padding yang sama dengan utility seperti pt-safe pb-safe px-safe pada satu shell tersebut.
Atur Capacitor iOS contentInset ke never dulu
Di capacitor.config.ts, lebih baik nonaktifkan native inset dan biarkan CSS (atau Native Navigation’s contentInsetMode: 'css') mengatur safe area:
const config: CapacitorConfig = {
appId: 'com.example.myapp',
appName: 'my-app',
webDir: 'out',
ios: {
contentInset: 'never',
},
};
Mencampur automatic content inset dari Capacitor dengan CSS env(safe-area-inset-*) padding sering kali menjadi penyebab spasi ganda.
Temukan elemen yang benar-benar overflow
Penyebab umumnya adalah elemen yang menggunakan 100vw, Tailwind w-screen, lebar pixel tetap, atau ukuran yang besar min-width.
Di Safari Web Inspector, jalankan:
[...document.querySelectorAll('*')]
.filter(el => el.scrollWidth > document.documentElement.clientWidth)
.map(el => ({
el,
tag: el.tagName,
class: el.className,
scrollWidth: el.scrollWidth,
clientWidth: document.documentElement.clientWidth,
}));
Dengan Tailwind, ganti w-screen dengan w-full jika memungkinkan. Banyak masalah overflow horizontal berasal dari 100vw / w-screen, duplikasi safe-area padding, atau container berukuran tetap — bukan dari viewport meta tag itu sendiri.
Update Over-the-Air
Siapkan Capgo untuk push update tanpa submit ulang ke app store:
bunx @capgo/cli init
Pemecahan Masalah
Build gagal dengan pesan “Cannot find module”
Jalankan bun install dan coba lagi.
iOS: “No signing identity found” Buka Xcode, masuk ke Signing & Capabilities, lalu pilih development team Anda.
Android: “SDK location tidak ditemukan”
Buat android/local.properties dengan sdk.dir=/path/to/android/sdk
Perubahan tidak muncul di perangkat
Pastikan Anda sudah menjalankan bun run mobile setelah melakukan perubahan. Untuk live reload, pastikan alamat IP sudah benar dan dev server sedang berjalan.
Sumber Daya
- Dokumentasi Capacitor 8
- Dokumentasi Next.js 15
- Capgo - Live Updates
- @capgo/capacitor-native-navigation
- @capgo/capacitor-transitions
- @capgo/tailwind-capacitor
Siap merilis aplikasi Anda? Pelajari bagaimana Capgo membantu Anda mengirim update lebih cepat — daftar akun gratis sekarang hari ini.
Lanjutkan dari Build a Next.js Mobile App from Scratch with Capacitor 8
Jika Anda menggunakan Membangun Mobile App Next.js dari Nol dengan Capacitor 8 untuk merencanakan otomatisasi CI/CD, hubungkan dengan CI/CD Capgo untuk workflow produk di Capgo CI/CD, Capgo Native Build untuk workflow produk di Capgo Native Builds, Integrasi Capgo untuk alur kerja produk di Integrasi Capgo, Integrasi CI/CD untuk detail implementasi di Integrasi CI/CD, dan Integrasi GitHub Actions untuk detail implementasi di Integrasi GitHub Actions.