Introduzione
Vuoi creare un'app mobile con Next.js da zero? Questa guida ti accompagna nella creazione di un nuovo progetto Next.js 15 configurato per il mobile fin dal primo giorno, per poi impacchettarlo come app native iOS e Android usando Capacitor 8.
Al termine di questo tutorial avrai un'app mobile funzionante sui simulatori, che potrai continuare a sviluppare e infine pubblicare su App Store e Google Play.
Tempo richiesto: ~30 minuti
Cosa creerai:
- Un nuovo progetto Next.js 15 con App Router
- Configurazione static export per mobile
- Capacitor 8 con plugin essenziali
- App native iOS e Android
- Setup di sviluppo con live reload
Hai già un'app Next.js? Guarda: Converti la tua app Next.js in Mobile invece.
Prerequisiti
Assicurati di avere installato:
- Node.js 18+ (verifica con
node --version) - Bun package manager ("
curl -fsSL https://bun.sh/install | bash) - Xcode (solo macOS, per lo sviluppo iOS)
- Android Studio (per lo sviluppo Android)
Passaggio 1: Crea un nuovo progetto Next.js
Inizia creando un nuovo progetto Next.js 15:
bunx create-next-app@latest my-mobile-app
Quando richiesto, seleziona queste opzioni:
- TypeScript: Sì (consigliato)
- ESLint: Sì
- Tailwind CSS: Sì (consigliato per lo styling mobile)
src/directory: Sì- App Router: Sì (consigliato)
- Alias di importazione: Default (
@/*)
Vai alla cartella del progetto:
cd my-mobile-app
Passaggio 2: Configura Next.js per l'export statico
Capacitor richiede file HTML/JS/CSS statici. Configura Next.js per l'export statico aggiornando 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;
Perché queste impostazioni?
output: 'export'— Genera HTML statico invece di richiedere un server Node.jsimages: { unoptimized: true }— Disabilita l'ottimizzazione delle immagini di Next.js (richiede un server)trailingSlash: true— Garantisce il routing corretto nella WebView nativa
Passaggio 3: Aggiungi gli script per mobile
Aggiorna il tuo package.json con gli script per lo sviluppo 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"
}
}
Testa la build:
bun run build
Dovresti vedere una cartella out cartella con i tuoi file statici.
Passaggio 4: Installa Capacitor 8
Installa i pacchetti core di Capacitor:
bun add @capacitor/core
bun add -D @capacitor/cli
Installa i plugin essenziali per la maggior parte delle app mobile:
bun add @capacitor/app @capacitor/keyboard @capacitor/splash-screen @capacitor/status-bar @capacitor/preferences
Cosa fanno questi plugin:
- @capacitor/app — Eventi del ciclo di vita dell'app (foreground/background, deep links)
- @capacitor/keyboard — Controllo del comportamento della tastiera
- @capacitor/splash-screen — Controllo della splash screen nativa
- @capacitor/status-bar — Personalizza la status bar del dispositivo
- @capacitor/preferences — Storage key-value (simile a localStorage ma nativo)
Passaggio 5: Inizializza Capacitor
Inizializza Capacitor con i dettagli del progetto:
bunx cap init "My Mobile App" com.example.mymobileapp --web-dir out
Sostituisci:
"My Mobile App"con il nome visualizzato della tua appcom.example.mymobileappcon l'ID della tua app (notazione reverse domain)
Questo crea capacitor.config.ts. Aggiornalo con la configurazione del 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;
Passaggio 6: Aggiungi piattaforme native
Installa i pacchetti della piattaforma:
bun add @capacitor/ios @capacitor/android
Genera i progetti nativi:
bunx cap add ios
bunx cap add android
Questo crea ios e android directory contenenti i progetti nativi.
Passaggio 7: Build ed esegui
Builda il progetto e sincronizzalo con le piattaforme native:
bun run mobile
Apri nell'iOS Simulator:
bun run mobile:ios
O nell'Android Emulator:
bun run mobile:android
In Xcode (iOS):
- Seleziona un simulatore dal menu a tendina dei dispositivi
- Clicca sul tasto Play o premi
Cmd + R
In Android Studio:
- Wait for Gradle to finish syncing
- Seleziona un emulatore dal menu a discesa dei dispositivi
- Clicca sul tasto Run o premi
Shift + F10
Passaggio 8: Configura Live Reload
Per uno sviluppo più rapido, abilita il live reload per vedere le modifiche istantaneamente sul dispositivo.
- Trova il tuo indirizzo IP locale:
# macOS
ipconfig getifaddr en0
# Windows
ipconfig
- Crea una config di sviluppo per Capacitor. Aggiungi a
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;
- Avvia il dev server e copia la config nel nativo:
bun run dev &
NODE_ENV=development bunx cap copy
- Ricompila in Xcode/Android Studio
Ora le modifiche al tuo code di Next.js verranno caricate via hot-reload sul dispositivo.
Passaggio 9: Crea la tua prima schermata mobile
Creiamo una semplice home screen ottimizzata per mobile. Aggiorna 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>
);
}
Passaggio 10: Aggiungi gestione Safe Area
I dispositivi mobile hanno notch, indicatori home e status bar. Aggiungi la gestione delle safe area con Tailwind.
Aggiornamento 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;
}
Struttura del progetto
Il tuo progetto ora dovrebbe apparire così:
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
└── ...
Prossimi passi
Ora hai una mobile app Next.js funzionante. Ecco i prossimi passi:
Setup essenziale
- Icone dell'app: Sostituisci le icone predefinite in
ios/App/App/Assets.xcassetseandroid/app/src/main/res - Splash screen: Personalizza i progetti nativi o usa
@capacitor/splash-screenconfig - Deep Links: Configura gli URL scheme per la tua app
Aggiungi altre feature
- Camera:
bun add @capacitor/camera - Geolocalizzazione:
bun add @capacitor/geolocation - Notifiche Push:
bun add @capacitor/push-notifications - File System:
bun add @capacitor/filesystem
UI e transizioni native
Usa i plugin Capgo invece di Konsta UI per un feeling mobile nativo:
- @capgo/capacitor-native-navigation — Tab bar Liquid Glass e navbar nativa
- @capgo/capacitor-transitions — transizioni di pagina dall'aspetto nativo
bun add @capgo/capacitor-native-navigation @capgo/capacitor-transitions
bunx cap sync
Per le safe areas di Tailwind, aggiungi @capgo/tailwind-capacitor:
bun add -D tailwind-capacitor
Vedi Usando @capgo/capacitor-native-navigation, Uso di @capgo/capacitor-transitions, e il il repo tailwind-capacitor per la configurazione specifica di Next.js.
Risolvere problemi di layout su iOS (Viewport, Safe Area e overflow orizzontale)
Se i contenuti appaiono tagliati, spostati o con scroll orizzontale su iOS, aggiungere più overflow-x: hidden o modificare il tag viewport di solito non basta. Segui questi controlli in ordine.
Assicurati che il meta tag viewport sia applicato correttamente
App Router (app/): export viewport da app/layout.tsx:
import type { Viewport } from 'next';
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
viewportFit: 'cover',
};
Pages Router (pages/): inserisci il meta tag viewport in pages/_app.tsx, non _document.tsx.
Gestisci la safe area di iOS da un unico wrapper root
Crea un unico app shell e applica lì il padding per la safe area — non in più componenti annidati:
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);
}
Racchiudi tutto il contenuto della pagina in .app-shell. Il padding della safe-area duplicato in header, modal e wrapper del layout spesso fa apparire l'interfaccia tagliata o troppo grande.
Con @capgo/tailwind-capacitor, puoi applicare lo stesso padding con utility come pt-safe pb-safe px-safe su quella singola shell.
Imposta Capacitor iOS contentInset a never per primo
In capacitor.config.ts, preferisci disabilitare i native inset e lascia che il CSS (o quella contentInsetMode: 'css'di Native Navigation) gestisca la safe area:
const config: CapacitorConfig = {
appId: 'com.example.myapp',
appName: 'my-app',
webDir: 'out',
ios: {
contentInset: 'never',
},
};
Mescolare l'automatic content inset di Capacitor con il CSS env(safe-area-inset-*) il padding è una causa comune di spaziature doppie.
Trova il vero elemento che causa l'overflow
Il colpevole di solito è un elemento che usa 100vw, Tailwind w-screen, una larghezza fissa in pixel, o un grande min-width.
In Safari Web Inspector, esegui:
[...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,
}));
Con Tailwind, sostituisci w-screen con w-full quando possibile. Molti problemi di overflow orizzontale derivano da 100vw / w-screen, padding della safe-area duplicato o un contenitore a larghezza fissa — non dal meta tag viewport stesso."
Aggiornamenti Over-the-Air
Configurazione Capgo per inviare aggiornamenti senza risottomissione agli store:
bunx @capgo/cli init
Risoluzione problemi
Il build fallisce con “Cannot find module”
Esegui bun install e riprova.
iOS: “No signing identity found” Apri Xcode, vai su Signing & Capabilities e seleziona il tuo team di sviluppo.
Android: “SDK location non trovata”
Crea android/local.properties con sdk.dir=/path/to/android/sdk
Le modifiche non appaiono sul dispositivo
Assicurati di aver eseguito bun run mobile dopo aver apportato le modifiche. Per il live reload, verifica che l'indirizzo IP sia corretto e che il dev server sia attivo.
Risorse
- Documentazione di Capacitor 8
- Documentazione di Next.js 15
- Capgo - Live Updates
- @capgo/capacitor-native-navigation
- @capgo/capacitor-transitions
- @capgo/tailwind-capacitor
Pronto a pubblicare la tua app? Scopri come Capgo può aiutarti a rilasciare aggiornamenti più velocemente — registrati per un account gratuito oggi.
Continua da Build a Next.js Mobile App from Scratch con Capacitor 8
Se stai usando Crea un'app mobile Next.js da zero con Capacitor 8 per pianificare l'automazione CI/CD e connetterla a CI/CD con Capgo per il workflow del prodotto in Capgo CI/CD, Capgo Native Build per il workflow di prodotto in Capgo Native Builds, Integrazioni Capgo per il workflow di prodotto in Integrazioni Capgo, Integrazione CI/CD per i dettagli implementativi in CI/CD Integration, e Integrazione GitHub Actions per i dettagli implementativi in GitHub Actions Integration.