Deployment - Producción

Llevar tu aplicación NextJS a producción implica preparar tu código, optimizarlo, y elegir dónde hospedarlo.

Build de producción

Antes de desplegar, crea un build optimizado:

npm run build

Este comando:

  1. Compila tu código TypeScript
  2. Optimiza imágenes
  3. Genera páginas estáticas
  4. Minimiza JavaScript y CSS
  5. Genera el bundle final

Output:

Route (app)                                Size     First Load JS
┌ ○ /                                      137 B          85.9 kB
├ ○ /productos                             2.34 kB        88.1 kB
├ ● /productos/[id]                        1.89 kB        87.7 kB
└ ƒ /api/productos/route                   0 B            0 B

○  (Static)   Generada en build time
●  (SSG)      Pre-renderizada con generateStaticParams
ƒ  (Dynamic)  Renderizada on-demand

Leyenda:

  • ○ Static: HTML estático, súper rápido
  • ● SSG: Static Site Generation con datos
  • ƒ Dynamic: Server-side rendering por request

Probar el build localmente

npm run build
npm run start

Abre http://localhost:3000 y prueba tu aplicación como estará en producción.

⚠️
Siempre prueba el build antes de desplegar

npm run dev es diferente a npm run build + npm start. Siempre prueba el build de producción localmente antes de desplegar para detectar errores.

Preparación para producción

1. Variables de entorno

Crea un archivo .env.production:

# .env.production
DATABASE_URL="postgresql://user:pass@host/db"
NEXT_PUBLIC_API_URL="https://api.mitienda.com"
SECRET_KEY="tu-secret-key-super-seguro"

Variables públicas vs privadas:

// ✅ Servidor (privada) - NO se expone al cliente
process.env.DATABASE_URL
process.env.SECRET_KEY

// ✅ Cliente (pública) - Se incluye en el bundle
process.env.NEXT_PUBLIC_API_URL
⚠️
Nunca expongas secretos

Variables sin NEXT_PUBLIC_ son privadas y solo funcionan en el servidor. Úsalas para API keys, database URLs, y secrets.

2. Configuración de Next.js

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Modo estricto de React
  reactStrictMode: true,
  
  // Dominios permitidos para imágenes
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'cdn.mitienda.com',
      },
    ],
  },
  
  // Redirecciones
  async redirects() {
    return [
      {
        source: '/old-route',
        destination: '/new-route',
        permanent: true,
      },
    ]
  },
  
  // Headers de seguridad
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'X-Frame-Options',
            value: 'DENY',
          },
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff',
          },
        ],
      },
    ]
  },
}

module.exports = nextConfig

3. Optimizar imágenes

Asegúrate de usar el componente Image:

// ❌ Mal
<img src="/producto.jpg" alt="Producto" />

// ✅ Bien
import Image from 'next/image'

<Image 
  src="/producto.jpg" 
  alt="Producto"
  width={500}
  height={300}
  priority  // Para imágenes above-the-fold
/>

4. Revisar bundle size

npm run build

# Busca advertencias como:
# ⚠ Compiled with warnings
# 
# ./app/page.tsx
# Module not found: Can't resolve 'huge-library'

Analizar bundle:

npm install @next/bundle-analyzer

# next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
})

module.exports = withBundleAnalyzer(nextConfig)
ANALYZE=true npm run build

Abre el reporte en el navegador y busca librerías grandes que puedas optimizar.

5. Configurar errores

// app/error.tsx
'use client'

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div>
      <h2>Algo salió mal</h2>
      <button onClick={reset}>Intentar de nuevo</button>
    </div>
  )
}
// app/not-found.tsx
export default function NotFound() {
  return (
    <div>
      <h2>404 - Página no encontrada</h2>
      <a href="/">Volver al inicio</a>
    </div>
  )
}

6. Robots.txt y Sitemap

// app/robots.ts
import { MetadataRoute } from 'next'

export default function robots(): MetadataRoute.Robots {
  return {
    rules: {
      userAgent: '*',
      allow: '/',
      disallow: '/admin/',
    },
    sitemap: 'https://mitienda.com/sitemap.xml',
  }
}
// app/sitemap.ts
import { MetadataRoute } from 'next'

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const productos = await fetch('https://api.mitienda.com/productos')
    .then(r => r.json())
  
  return [
    {
      url: 'https://mitienda.com',
      lastModified: new Date(),
      priority: 1,
    },
    ...productos.map((p) => ({
      url: `https://mitienda.com/productos/${p.id}`,
      lastModified: p.updatedAt,
      priority: 0.8,
    })),
  ]
}

Opciones de hosting

1. Vercel (recomendado)

Ventajas:

  • ✅ Deploy automático desde Git
  • ✅ Preview deployments
  • ✅ Edge functions
  • ✅ Analytics incluido
  • ✅ Zero-config

Ideal para:

  • Cualquier app NextJS
  • Equipos pequeños y medianos
  • Prototipado rápido

Precio:

  • Gratis para hobby
  • Pro: $20/mes por usuario

→ Guía completa de Vercel

2. Netlify

Ventajas:

  • ✅ Deploy automático
  • ✅ Formularios integrados
  • ✅ Functions serverless

Configuración:

# netlify.toml
[build]
  command = "npm run build"
  publish = ".next"

[[plugins]]
  package = "@netlify/plugin-nextjs"

3. AWS (Amplify)

Ventajas:

  • ✅ Integración con servicios AWS
  • ✅ CDN global
  • ✅ Escalable

Para:

  • Apps enterprise
  • Necesitas integración AWS

4. Docker (self-hosting)

Dockerfile:

FROM node:18-alpine AS base

FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM base AS runner
WORKDIR /app
ENV NODE_ENV production

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs
EXPOSE 3000
ENV PORT 3000

CMD ["node", "server.js"]
// next.config.js
module.exports = {
  output: 'standalone',
}
docker build -t mi-app .
docker run -p 3000:3000 mi-app

5. VPS (DigitalOcean, Linode)

Setup básico:

# En tu servidor
git clone https://github.com/tuusuario/tu-app
cd tu-app
npm install
npm run build

# Usar PM2 para mantener la app corriendo
npm install -g pm2
pm2 start npm --name "mi-app" -- start
pm2 save
pm2 startup

Nginx como reverse proxy:

# /etc/nginx/sites-available/mi-app
server {
  listen 80;
  server_name mitienda.com;

  location / {
    proxy_pass http://localhost:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
  }
}

6. Static Export

Para sitios 100% estáticos (sin servidor):

// next.config.js
module.exports = {
  output: 'export',
}

Ideal para:

  • Blogs
  • Landing pages
  • Documentación
  • Portfolios

→ Guía completa de Static Exports

Comparación de opciones

OpciónFacilidadPrecioEscalabilidadServer ComponentsServer Actions
Vercel⭐⭐⭐⭐⭐$0-$20⭐⭐⭐⭐⭐
Netlify⭐⭐⭐⭐⭐$0-$19⭐⭐⭐⭐
AWS⭐⭐⭐Variable⭐⭐⭐⭐⭐
Docker⭐⭐Depende⭐⭐⭐⭐
VPS⭐⭐$5-$50⭐⭐⭐
Static⭐⭐⭐⭐⭐$0⭐⭐⭐

Monitoreo y analytics

Vercel Analytics

// app/layout.tsx
import { Analytics } from '@vercel/analytics/react'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Analytics />
      </body>
    </html>
  )
}

Vercel Speed Insights

import { SpeedInsights } from '@vercel/speed-insights/next'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <SpeedInsights />
      </body>
    </html>
  )
}

Sentry (errores)

npm install @sentry/nextjs
npx @sentry/wizard@latest -i nextjs
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs'

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 1.0,
})

Checklist de producción

Antes de desplegar:

Performance:

  • Ejecuta npm run build sin errores
  • Páginas críticas < 100 KB First Load JS
  • Imágenes optimizadas con <Image>
  • Fonts optimizadas con next/font

SEO:

  • Metadata en todas las páginas
  • Sitemap.xml configurado
  • Robots.txt configurado
  • Open Graph images

Seguridad:

  • Variables de entorno configuradas
  • API keys en .env, no hardcodeadas
  • Headers de seguridad configurados
  • HTTPS activado

Funcionalidad:

  • Páginas de error (404, 500)
  • Loading states
  • Manejo de errores
  • Pruebas E2E pasando

Monitoreo:

  • Analytics configurado
  • Error tracking (Sentry)
  • Speed insights

Variables de entorno en producción

Vercel

# CLI
vercel env add NOMBRE_VARIABLE

# O en dashboard: Settings → Environment Variables

Netlify

# CLI
netlify env:set NOMBRE_VARIABLE valor

# O en dashboard: Site settings → Environment variables

Docker

docker run -e DATABASE_URL="..." -e SECRET_KEY="..." mi-app

Dominios personalizados

Vercel

  1. Ve a Settings → Domains
  2. Agrega tu dominio
  3. Configura DNS:
Type: A
Name: @
Value: 76.76.21.21

Type: CNAME
Name: www
Value: cname.vercel-dns.com

Cloudflare (proxy)

Si usas Cloudflare:

Type: CNAME
Name: @
Value: cname.vercel-dns.com
Proxy: ON (naranja)

CI/CD

GitHub Actions

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: 18
      
      - name: Install
        run: npm ci
      
      - name: Build
        run: npm run build
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
      
      - name: Deploy
        run: npx vercel --prod
        env:
          VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}

Troubleshooting

Build falla

# Error: Module not found
→ Verifica imports y paths

# Error: Out of memory
→ Aumenta memoria: NODE_OPTIONS="--max-old-space-size=4096"

# Error: Type errors
→ Ejecuta: npm run type-check

Performance lenta

# Analiza bundle
ANALYZE=true npm run build

# Revisa métricas en Vercel Speed Insights
# Lighthouse en Chrome DevTools

Imágenes no cargan

// Verifica dominios permitidos
// next.config.js
images: {
  remotePatterns: [
    {
      protocol: 'https',
      hostname: 'tu-cdn.com',
    },
  ],
}

Mejores prácticas

1. Usa preview deployments

# Vercel crea preview por cada PR
# Prueba cambios antes de mergear

2. Monitorea errores

// Sentry captura errores automáticamente
// Revisa dashboard regularmente

3. Optimiza continuamente

# Lighthouse CI en cada deploy
# Mantén score > 90

4. Cache agresivamente

// Revalidación apropiada
export const revalidate = 3600 // 1 hora

5. Backups de base de datos

# Automatiza backups diarios
# Prueba restore regularmente

Resumen

Para llevar a producción:

  1. npm run build - Crea build optimizado
  2. Prueba localmente con npm start
  3. Configura variables de entorno
  4. Elige hosting (Vercel recomendado)
  5. Configura dominio personalizado
  6. Activa monitoreo

Hosting recomendado:

  • Vercel: Mejor opción para mayoría de casos
  • Netlify: Alternativa sólida
  • Docker/VPS: Para control total
  • Static: Para sitios sin servidor

Próximos pasos: