Langsung ke konten utama
Tutorial

Build Nuxt Mobile App dari Nol dengan Capacitor 8

Panduan step-by-step membuat proyek Nuxt 4 baru dan mengubahnya menjadi aplikasi mobile iOS dan Android native menggunakan Capacitor 8. Cocok untuk memulai pengembangan Vue berbasis mobile.

Bangun Nuxt Mobile App dari Nol dengan Capacitor 8 Capgo ilustrasi blog

Pendahuluan

Ingin membangun mobile app dengan Nuxt dari awal? Panduan ini akan memandu Anda membuat proyek Nuxt 4 baru yang dikonfigurasi untuk mobile sejak hari pertama, lalu mengemasnya sebagai aplikasi native iOS dan Android menggunakan Capacitor 8.

Di akhir tutorial ini, Anda akan memiliki mobile app yang berjalan di simulator untuk terus dikembangkan dan nantinya dipublikasikan ke App Store dan Google Play.

Waktu yang dibutuhkan: ~30 menit

Apa yang akan Anda build:

  • Proyek Nuxt 4 baru dengan struktur direktori terbaru
  • Konfigurasi static generation untuk mobile
  • Capacitor 8 dengan plugin esensial
  • Aplikasi native iOS dan Android
  • Setup development live reload

Sudah punya aplikasi Nuxt? Cek Konversi Aplikasi Nuxt 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 (hanya macOS, untuk pengembangan iOS)
  • Android Studio (untuk pengembangan Android)

Langkah 1: Buat Proyek Nuxt 4 Baru

Mulai dengan membuat proyek Nuxt 4 baru:

bunx nuxi@latest init my-mobile-app
cd my-mobile-app
bun install

Struktur Direktori Nuxt 4

Nuxt 4 menggunakan struktur direktori baru dengan app code di app/ direktori:

my-mobile-app/
  app/
    assets/
    components/
    composables/
    layouts/
    middleware/
    pages/
    plugins/
    utils/
    app.vue
  public/
  server/
  nuxt.config.ts
  package.json

Struktur ini memberikan pemisahan yang lebih baik antara app dan server code.

Langkah 2: Konfigurasi Nuxt untuk Static Generation

Capacitor memerlukan file HTML/JS/CSS statis. Konfigurasi Nuxt untuk static generation di nuxt.config.ts:

export default defineNuxtConfig({
  compatibilityDate: '2025-01-15',
  devtools: { enabled: true },

  // Enable static generation
  ssr: true,
  nitro: {
    preset: 'static',
  },
});

Langkah 3: Tambahkan Mobile Scripts

Perbarui file package.json dengan mobile development scripts:

{
  "scripts": {
    "dev": "nuxt dev",
    "build": "nuxt build",
    "generate": "nuxt generate",
    "preview": "nuxt preview",
    "mobile": "bun run generate && bunx cap sync",
    "mobile:ios": "bun run mobile && bunx cap open ios",
    "mobile:android": "bun run mobile && bunx cap open android"
  }
}

Tes static generation:

bun run generate

Anda akan melihat .output/public direktori berisi file statis Anda.

Langkah 4: Install 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 aplikasi (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 .output/public

Ganti:

  • "My Mobile App" dengan nama tampilan aplikasi Anda
  • com.example.mymobileapp dengan 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: '.output/public',
  plugins: {
    SplashScreen: {
      launchShowDuration: 2000,
      launchAutoHide: true,
      androidScaleType: 'CENTER_CROP',
      splashFullScreen: true,
      splashImmersive: true,
    },
    Keyboard: {
      resize: 'body',
      resizeOnFullScreen: true,
    },
    StatusBar: {
      style: 'dark',
    },
  },
};

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):

  1. Pilih simulator dari dropdown perangkat
  2. Klik tombol Play atau tekan Cmd + R

Di Android Studio:

  1. Tunggu Gradle selesai sinkronisasi
  2. Pilih emulator dari dropdown perangkat
  3. 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.

  1. Temukan alamat IP lokal Anda:
# macOS
ipconfig getifaddr en0

# Windows
ipconfig
  1. Buat konfigurasi Capacitor development. Update capacitor.config.ts:
import type { CapacitorConfig } from '@capacitor/cli';

const devConfig: CapacitorConfig = {
  appId: 'com.example.mymobileapp',
  appName: 'My Mobile App',
  webDir: '.output/public',
  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: '.output/public',
  plugins: {
    // ... same plugin config
  },
};

const config = process.env.NODE_ENV === 'development' ? devConfig : prodConfig;

export default config;
  1. Jalankan dev server dan salin konfigurasi ke native:
bun run dev &
NODE_ENV=development bunx cap copy
  1. Rebuild di Xcode/Android Studio

Sekarang, setiap perubahan pada Nuxt code Anda akan hot-reload di perangkat.

Langkah 9: Buat Layar Mobile Pertama Anda

Mari buat home screen yang mobile-friendly. Update app/app.vue:

<template>
  <NuxtPage />
</template>

Buat app/pages/index.vue:

<template>
  <main
    class="min-h-screen bg-linear-to-b from-green-500 to-green-700 flex flex-col items-center justify-center p-6 text-white"
  >
    <h1 class="text-4xl font-bold mb-4">My Mobile App</h1>
    <p class="text-xl mb-8 text-center opacity-90">
      Built with Nuxt 4 + Capacitor 8
    </p>

    <div v-if="appInfo" class="bg-white/20 rounded-lg p-4 backdrop-blur-sm mb-8">
      <p class="text-sm">
        {{ appInfo.name }} v{{ appInfo.version }}
      </p>
    </div>

    <div class="space-y-4 w-full max-w-sm">
      <button
        class="w-full py-4 px-6 bg-white text-green-600 rounded-xl font-semibold text-lg shadow-lg active:scale-95 transition-transform"
        @click="handleGetStarted"
      >
        Get Started
      </button>
      <button
        class="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"
        @click="handleShare"
      >
        Share App
      </button>
    </div>
  </main>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { App } from '@capacitor/app';

const appInfo = ref<{ name: string; version: string } | null>(null);

let backButtonListener: { remove: () => void } | null = null;

onMounted(async () => {
  // Get app info
  try {
    appInfo.value = await App.getInfo();
  } catch (e) {
    // Web fallback
    appInfo.value = { name: 'My Mobile App', version: '1.0.0' };
  }

  // Handle Android back button
  backButtonListener = await App.addListener('backButton', ({ canGoBack }) => {
    if (!canGoBack) {
      App.exitApp();
    } else {
      window.history.back();
    }
  });
});

onUnmounted(() => {
  backButtonListener?.remove();
});

function handleGetStarted() {
  // Navigate to onboarding or main app
  console.log('Get started clicked');
}

async function handleShare() {
  // We'll implement this with the Share plugin later
  console.log('Share clicked');
}
</script>

Langkah 10: Tambahkan Tailwind CSS

Agar styling berfungsi, tambahkan Tailwind CSS ke proyek Anda:

bun add tailwindcss @tailwindcss/vite

Update nuxt.config.ts:

import tailwindcss from '@tailwindcss/vite';

export default defineNuxtConfig({
  compatibilityDate: '2025-01-15',
  devtools: { enabled: true },

  ssr: true,
  nitro: {
    preset: 'static',
  },

  css: ['~/assets/css/main.css'],

  vite: {
    plugins: [tailwindcss()],
  },
});

Buat app/assets/css/main.css:

@import 'tailwindcss';

: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;
}

Langkah 11: Tambahkan Plugin Share

Mari implementasikan fungsi tombol share:

bun add @capacitor/share

Update app/pages/index.vue untuk menggunakan plugin Share:

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { App } from '@capacitor/app';
import { Share } from '@capacitor/share';

// ... existing code ...

async function handleShare() {
  try {
    await Share.share({
      title: 'Check out this app!',
      text: 'Built with Nuxt 4 and Capacitor 8',
      url: 'https://capacitorjs.com',
      dialogTitle: 'Share with friends',
    });
  } catch (e) {
    console.log('Share cancelled or failed:', e);
  }
}
</script>

Sync dan rebuild:

bun run mobile

Struktur Proyek

Proyek Anda sekarang seharusnya terlihat seperti ini:

my-mobile-app/
├── android/                  # Android native project
├── ios/                      # iOS native project
├── .output/
│   └── public/              # Static build output
├── app/
│   ├── assets/
│   │   └── css/
│   │       └── main.css
│   ├── pages/
│   │   └── index.vue
│   └── app.vue
├── capacitor.config.ts       # Capacitor configuration
├── nuxt.config.ts            # Nuxt configuration
├── package.json
└── ...

Langkah Berikutnya

Sekarang Anda sudah memiliki aplikasi mobile Nuxt yang berjalan. Berikut langkah selanjutnya:

Setup Esensial

  • Ikon Aplikasi: Ganti ikon default di ios/App/App/Assets.xcassets dan android/app/src/main/res
  • Splash Screen: Kustomisasi di native project atau gunakan @capacitor/splash-screen config
  • 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 atau @capgo/capacitor-firebase-messaging untuk Firebase Cloud Messaging di iOS dan Android
  • File System: bun add @capacitor/filesystem

UI dan transisi native

Gunakan plugin Capgo alih-alih Konsta UI agar terasa seperti aplikasi native:

bun add @capgo/capacitor-native-navigation @capgo/capacitor-transitions
bunx cap sync

Untuk Tailwind safe areas, 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 Nuxt.

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

Di nuxt.config.ts, atur viewport melalui app.head:

export default defineNuxtConfig({
  app: {
    head: {
      meta: [
        {
          name: 'viewport',
          content: 'width=device-width, initial-scale=1, viewport-fit=cover',
        },
      ],
    },
  },
});

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,
#__nuxt {
  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: '.output/public',
  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 &amp; 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.

.output/public kosong atau tidak ditemukan Pastikan Anda sudah mengonfigurasi nitro: { preset: 'static' } di nuxt.config.ts dan jalankan bun run generate.

Sumber Daya

Siap rilis aplikasi Anda? Pelajari cara Capgo membantu Anda kirim update lebih cepat — daftar akun gratis sekarang hari ini.

Lanjutkan dari Build a Nuxt Mobile App from Scratch with Capacitor 8

Jika Anda menggunakan Bangun Nuxt Mobile App 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.

Live updates untuk aplikasi Capacitor

Saat bug web-layer sudah live, kirim perbaikannya lewat Capgo alih-alih menunggu berhari-hari untuk persetujuan app store. Pengguna mendapat update di background, sementara perubahan native tetap mengikuti jalur review normal.

Terbaru dari Blog kami

Capgo memberikan insight terbaik yang Anda butuhkan untuk membuat aplikasi mobile yang benar-benar profesional.