Saltar al contenido principal
Tutorial

Crea una app móvil con Next.js desde cero con Capacitor 8

Guía paso a paso para crear un proyecto Next.js 15 y convertirlo en apps nativas para iOS y Android con Capacitor 8. Ideal para empezar desde cero con un enfoque mobile-first.

Crea una app móvil con Next.js desde cero con Capacitor 8 Capgo

Introducción

¿Quieres crear una app móvil con Next.js desde cero? Esta guía te enseña a configurar un proyecto de Next.js 15 para móvil desde el primer día y empaquetarlo como apps nativas de iOS y Android usando Capacitor 8.

Al terminar este tutorial, tendrás una app móvil funcionando en simuladores que podrás seguir desarrollando y, finalmente, publicar en la App Store y Google Play.

Tiempo necesario: ~30 minutos

Qué vas a construir:

  • Un nuevo proyecto Next.js 15 con App Router
  • Configuración de exportación estática para móvil
  • Capacitor 8 con plugins esenciales
  • Apps nativas para iOS y Android
  • Entorno de desarrollo con Live reload

¿Ya tienes una app en Next.js? Mira esto: Convierte tu app de Next.js a móvil en su lugar.

Requisitos

Asegúrate de tener instalado lo siguiente:

  • Node.js 18+ (comprueba con tu node --version)
  • Bun gestor de paquetes (curl -fsSL https://bun.sh/install | bash)
  • Xcode (solo macOS, para desarrollo iOS)
  • Android Studio (para desarrollo Android)

Paso 1: Crea un nuevo proyecto Next.js

Empieza creando un nuevo proyecto Next.js 15:

bunx create-next-app@latest my-mobile-app

Cuando aparezca el prompt, selecciona estas opciones:

  • TypeScript: Sí (recomendado)
  • ESLint: Sí
  • Tailwind CSS: Sí (recomendado para estilos móviles)
  • src/ directorio: Sí
  • App Router: Sí (recomendado)
  • Alias de importación: Por defecto (@/*)

Navega a tu proyecto:

cd my-mobile-app

Paso 2: Configurar Next.js para exportación estática

Capacitor requiere archivos HTML/JS/CSS estáticos. Configura Next.js para exportación estática actualizando 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;

¿Por qué estos ajustes?

  • output: 'export' — Genera HTML estático en lugar de requerir un servidor Node.js
  • images: { unoptimized: true } — Desactiva la optimización de imágenes de Next.js (requiere un servidor)
  • trailingSlash: true — Asegura el enrutamiento correcto en la WebView nativa

Paso 3: Añadir scripts móviles

Actualiza tu package.json con scripts de desarrollo móvil:

{
  "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"
  }
}

Prueba el build:

bun run build

Deberías ver una carpeta out directorio con tus archivos estáticos.

Paso 4: Instalar Capacitor 8

Instala los paquetes core de Capacitor:

bun add @capacitor/core
bun add -D @capacitor/cli

Instala los plugins esenciales para la mayoría de las apps móviles:

bun add @capacitor/app @capacitor/keyboard @capacitor/splash-screen @capacitor/status-bar @capacitor/preferences

Qué hacen estos plugins:

  • @capacitor/app — Eventos del ciclo de vida de la app (foreground/background, deep links)
  • @capacitor/keyboard — Control del comportamiento del teclado
  • @capacitor/splash-screen — Control de la splash screen nativa
  • @capacitor/status-bar — Estilizar la barra de estado del dispositivo
  • @capacitor/preferences — Almacenamiento clave-valor (como localStorage pero nativo)

Paso 5: Inicializar Capacitor

Inicializa Capacitor con los detalles de tu proyecto:

bunx cap init "My Mobile App" com.example.mymobileapp --web-dir out

Reemplaza:

  • "My Mobile App" con el nombre visible de tu app
  • com.example.mymobileapp con el ID de tu app (notación de dominio inverso)

Esto crea capacitor.config.ts. Actualízalo con la configuración 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;

Paso 6: Añadir plataformas nativas

Instala los paquetes de la plataforma:

bun add @capacitor/ios @capacitor/android

Genera los proyectos nativos:

bunx cap add ios
bunx cap add android

Esto crea ios y android directorios que contienen los proyectos nativos.

Paso 7: Compilar y ejecutar

Compila tu proyecto y sincroniza con las plataformas nativas:

bun run mobile

Abrir en el simulador de iOS:

bun run mobile:ios

O en el emulador de Android:

bun run mobile:android

En Xcode (iOS):

  1. Selecciona un simulador del desplegable de dispositivos
  2. Haz clic en el botón Play o presiona Cmd + R

En Android Studio:

  1. Espera a que Gradle termine de sincronizar
  2. Selecciona un emulador en el desplegable de dispositivos
  3. Haz clic en el botón Run o presiona Shift + F10

Paso 8: Configura Live Reload

Para desarrollar más rápido, activa el live reload y los cambios se verán al instante en tu dispositivo.

  1. Busca tu dirección IP local:
# macOS
ipconfig getifaddr en0

# Windows
ipconfig
  1. Crea una configuración de desarrollo para Capacitor. Añádela 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;
  1. Inicia el servidor de dev y copia la config al nativo:
bun run dev &
NODE_ENV=development bunx cap copy
  1. Reconstruye en Xcode/Android Studio

Ahora, los cambios en tu code de Next.js tendrán hot-reload en el dispositivo.

Paso 9: Crea tu primera pantalla móvil

Creemos una home sencilla optimizada para móviles. Actualiza 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>
  );
}

Paso 10: Gestión de Safe Areas

Los dispositivos móviles tienen notches, indicadores de inicio y barras de estado. Añade el manejo de safe areas con Tailwind.

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

Estructura del proyecto

Tu proyecto debería verse así ahora:

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
└── ...

Próximos pasos

Ya tienes una app móvil con Next.js funcionando. Esto es lo que sigue:

Configuración esencial

  • Iconos de la app: Reemplaza los iconos por defecto en ios/App/App/Assets.xcassets y android/app/src/main/res
  • Pantalla splash: Personaliza en proyectos nativos o usa @capacitor/splash-screen configuración
  • Deep Links: Configura los URL schemes de tu app

Añade más funciones

  • Cámara: bun add @capacitor/camera
  • Geolocalización: bun add @capacitor/geolocation
  • Notificaciones push: bun add @capacitor/push-notifications
  • Sistema de archivos: bun add @capacitor/filesystem

UI y transiciones nativas

Usa plugins de Capgo en vez de Konsta UI para una sensación nativa en móvil:

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

Para safe areas con Tailwind, añade @capgo/tailwind-capacitor:

bun add -D tailwind-capacitor

Ver Uso de @capgo/capacitor-native-navigation, Uso de @capgo/capacitor-transitions, y la repo de tailwind-capacitor para la configuración específica de Next.js.

Cómo solucionar problemas de layout en iOS (viewport, safe area y overflow horizontal)

Si el contenido se ve recortado, desplazado o con scroll horizontal en iOS, añadir más overflow-x: hidden o ajustar solo la etiqueta viewport no suele solucionarlo. Revisa estas comprobaciones en orden.

Asegúrate de que la meta tag viewport esté bien aplicada

App Router (app/): export viewport de los app/layout.tsx:

import type { Viewport } from 'next';

export const viewport: Viewport = {
  width: 'device-width',
  initialScale: 1,
  viewportFit: 'cover',
};

Pages Router (pages/): coloca el meta tag del viewport en pages/_app.tsx, no _document.tsx.

Gestiona el safe area de iOS desde un único wrapper raíz

Crea un único app shell y aplica el padding de safe area ahí, no en múltiples componentes anidados:

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

Envuelve todo el contenido de la página dentro de .app-shell. El padding de safe-area duplicado en headers, modales y wrappers de layout suele hacer que la UI parezca recortada o demasiado grande.

Con @capgo/tailwind-capacitor, puedes aplicar el mismo padding usando utilidades como pt-safe pb-safe px-safe en ese único shell.

Configura Capacitor iOS contentInset a never primero

En capacitor.config.ts, prefiere desactivar el inset nativo y dejar que CSS (o la navegación nativa) contentInsetMode: 'css') se encargue del safe area:

const config: CapacitorConfig = {
  appId: 'com.example.myapp',
  appName: 'my-app',
  webDir: 'out',
  ios: {
    contentInset: 'never',
  },
};

Combinar el content inset automático de Capacitor con CSS env(safe-area-inset-*) es una causa común de espaciado doble.

Encuentra el elemento que realmente desborda

El culpable habitual es un elemento que usa 100vw, Tailwind w-screen, un ancho fijo en píxeles, o un min-width.

En Safari Web Inspector, ejecuta:

[...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, reemplaza w-screen con w-full siempre que sea posible. Muchos problemas de desbordamiento horizontal vienen de 100vw / w-screen, padding de safe-area duplicado o un contenedor de ancho fijo, no del propio meta tag del viewport.

Actualizaciones OTA

Configura Capgo para enviar actualizaciones sin reenviar la app a la tienda:

bunx @capgo/cli init

Solución de problemas

El build falla con “Cannot find module” Ejecuta bun install e inténtalo de nuevo.

iOS: “No se encontró ninguna identidad de firma” Abre Xcode, ve a Signing &amp; Capabilities y selecciona tu equipo de desarrollo.

Android: “SDK ubicación no encontrada” Crear android/local.properties con sdk.dir=/path/to/android/sdk

Los cambios no aparecen en el dispositivo Asegúrate de haber ejecutado bun run mobile tras realizar cambios. Para live reload, verifica que la IP sea correcta y el dev server esté corriendo.

Recursos

¿Listo para lanzar tu app? Descubre cómo Capgo te ayuda a entregar updates más rápido — crea una cuenta gratis hoy.

Continuar con Build a Next.js Mobile App from Scratch with Capacitor 8

Si estás usando Crea una app móvil con Next.js desde cero con Capacitor 8 para planificar la automatización de CI/CD, conéctalo con CI/CD de Capgo para el flujo de trabajo del producto en Capgo CI/CD, Native Builds de Capgo para el flujo de trabajo del producto en Capgo Native Builds, Integraciones de Capgo para el flujo de trabajo del producto en Capgo Integraciones, Integración CI/CD para los detalles de implementación de CI/CD Integration, y Integración con GitHub Actions para los detalles de implementación de GitHub Actions Integration.

Live updates para apps Capacitor

Cuando un bug de la capa web está en producción, publica el fix a través de Capgo en lugar de esperar días a la aprobación de la app store. Los usuarios reciben la actualización en segundo plano mientras los cambios nativos siguen el flujo de revisión habitual.

Lo último de nuestro blog

Capgo te da la información clave que necesitas para crear una app móvil realmente profesional.