Del Código a Producción
La Mentalidad de Producción
Tu app funciona perfectamente en localhost. Felicidades — has superado el primer obstáculo. Pero aquí viene la dosis de realidad: localhost es un mundo de fantasía. No hay actores maliciosos intentando inyectar SQL en tus formularios. No hay usuarios con conexiones 3G inestables en zonas rurales. No hay nadie bombardeando tu API con 10,000 peticiones por segundo. Solo estás tú, tu navegador y una reconfortante sensación de control.
Producción es diferente. Producción significa usuarios reales con datos reales generando tráfico real, y cada uno de esos "reales" introduce problemas en los que nunca habías pensado. Un formulario que funciona de maravilla cuando tú lo llenas se rompe espectacularmente cuando alguien pega 50,000 caracteres de emojis en el campo de nombre. Una página que carga instantáneamente en tu MacBook M3 se arrastra en un teléfono Android de cinco años. Un endpoint de API que maneja tus datos de prueba con elegancia se ahoga cuando la base de datos tiene un millón de filas.
La mentalidad de producción no se trata de miedo — se trata de respeto. Respeto por tus usuarios, respeto por sus datos, y respeto por el hecho de que las cosas van a fallar. Tu trabajo es asegurarte de que cuando fallen, tú te enteres antes de que tus usuarios empiecen a quejarse en redes sociales.
La buena noticia: como vibe coder, no necesitas memorizar cada configuración de producción. Necesitas saber qué importa y cómo pedirle a la IA que lo implemente correctamente. De eso se trata esta lección.
Lista de Verificación para Producción
Antes de hacer deploy de cualquier cosa, revisa esta lista. Imprímela, pégala en tu pared, o mejor aún, conviértela en una plantilla de issue en GitHub. Cada elemento aquí es algo que le ha causado problemas a algún desarrollador en producción:
Manejo de Errores:
- Todas las rutas de API devuelven respuestas de error apropiadas (no stack traces)
- Los error boundaries del lado del cliente capturan fallos de renderizado
- La validación de formularios muestra mensajes amigables para el usuario
- Los fallos de red se manejan con elegancia mediante lógica de reintentos
Seguridad:
- Todas las variables de entorno están correctamente configuradas (nunca hardcodeadas)
- HTTPS está forzado en todas partes
- Los headers de seguridad están configurados (los cubriremos más adelante)
- Existe validación de entrada tanto en el cliente como en el servidor
- Los vectores de SQL injection y XSS están mitigados
- Los tokens de autenticación tienen expiración adecuada
Rendimiento:
- Las imágenes están optimizadas (formatos correctos, tamaños responsivos)
- Las fuentes están optimizadas (auto-hospedadas, display swap)
- Existen estados de carga para todas las operaciones asíncronas
- El tamaño del bundle es razonable (nada de importar todo lodash por una sola función)
Experiencia de Usuario:
- Existe una página 404 personalizada
- Existe una página de error 500 personalizada
- El favicon está configurado
- Las meta tags están configuradas para cada página
- Las imágenes Open Graph funcionan para compartir en redes sociales
- La app es responsiva en móvil
Operaciones:
- El monitoreo de errores está configurado (Sentry o similar)
- La analítica está rastreando eventos clave
- Las variables de entorno están configuradas en tu plataforma de hosting
- Los backups de la base de datos están automatizados
Esto parece mucho. Y lo es. Pero aquí está el enfoque del vibe coding: pega esta lista en tu herramienta de IA y di "audita mi proyecto contra esta lista y arregla todo lo que falte." Te sorprenderá cuánto puede manejar en una sola pasada.
CI/CD con GitHub Actions
Qué es CI/CD
CI/CD significa Integración Continua y Despliegue Continuo. En términos simples: cada vez que haces push de código, un robot revisa tu trabajo y (si todo pasa) lo despliega automáticamente.
Sin CI/CD, tu proceso de despliegue se ve así: terminas una funcionalidad, recuerdas ejecutar los tests, esperas no haber olvidado nada, haces deploy manualmente, rezas para que nada se rompa. Con CI/CD, haces push del código y el pipeline se encarga de todo: linting, verificación de tipos, testing, building y deploying. Si algo falla, recibes una notificación antes de que el código roto llegue a tus usuarios.
Creando Tu Flujo de CI
GitHub Actions usa archivos YAML en un directorio .github/workflows/. Aquí tienes un buen flujo inicial:
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Type check
run: npm run typecheck
- name: Run tests
run: npm run test
- name: Build
run: npm run build
Este flujo se activa en cada push a main y en cada pull request dirigido a main. Ejecuta tu linter, compilador de TypeScript, tests y build — en ese orden. Si cualquier paso falla, el pipeline se detiene y ves exactamente qué se rompió.
El Prompt Que Escribe Tu CI
Aquí tienes el prompt que puedes usar:
Crea un flujo de trabajo de CI con GitHub Actions para mi proyecto Next.js. Debe:
- Ejecutarse en pushes a main y en pull requests
- Instalar dependencias con npm ci
- Ejecutar lint, typecheck, tests y build en secuencia
- Cachear node_modules para ejecuciones más rápidas
- Usar Node.js 20
Guárdalo en .github/workflows/ci.yml
La IA generará el archivo y frecuentemente agregará mejoras en las que no habías pensado, como estrategias de caché o jobs paralelos para proyectos más grandes.
Desplegando en Vercel
Vercel es la empresa detrás de Next.js, y su plataforma está construida específicamente para desplegar aplicaciones Next.js. La experiencia de despliegue es lo más cercano a la magia que existe en hosting web.
Conectando Tu Repositorio
- Ve a vercel.com e inicia sesión con GitHub
- Haz clic en "Add New Project"
- Selecciona tu repositorio
- Vercel detecta automáticamente que es un proyecto Next.js y configura los ajustes de build
- Haz clic en "Deploy"
Eso es todo. Tu app está en vivo en un dominio .vercel.app en minutos.
Variables de Entorno
Tu archivo .env.local no se despliega (está en .gitignore, como debe ser). Necesitas agregar esas variables en el dashboard de Vercel:
- Ve a la configuración de tu proyecto
- Navega a "Environment Variables"
- Agrega cada variable con su valor
- Elige a qué entornos aplica: Production, Preview o Development
Vercel te da tres entornos. Production es tu sitio en vivo. Preview se genera automáticamente para cada pull request. Development es para el desarrollo local con vercel dev.
Despliegues de Vista Previa
Esta es una de las funcionalidades estrella de Vercel. Cada vez que abres un pull request, Vercel automáticamente construye y despliega una versión preview con su propia URL única. Tu equipo puede revisar la aplicación funcionando, no solo el diff del código. Cuando haces merge del PR, el preview se limpia automáticamente.
Configuración del Build
La mayoría del tiempo, la detección automática de Vercel funciona perfectamente. Si necesitas personalizar:
- Build Command:
npm run build(onext build) - Output Directory:
.next(detectado automáticamente) - Install Command:
npm ci - Root Directory: Generalmente
.a menos que tengas un monorepo
Configuración de Dominio Personalizado
Una URL .vercel.app funciona para pruebas, pero para un producto real necesitas un dominio real.
Comprar un Dominio
Puedes comprar dominios en registradores como Namecheap, Cloudflare Registrar, o directamente a través de Vercel. Cloudflare suele ser el más barato porque vende a precio de costo. Vercel es el más conveniente porque la configuración de DNS es automática.
Configurar DNS
Si compraste tu dominio en otro lugar, necesitas apuntarlo a Vercel. Tienes dos opciones:
Opción 1: Nameservers (recomendado) Cambia los nameservers de tu dominio a los de Vercel. Esto le da a Vercel control total sobre el DNS y hace que la configuración sea automática.
Opción 2: Registros DNS Agrega estos registros en tu registrador:
- Registro A:
@apuntando a76.76.21.21 - Registro CNAME:
wwwapuntando acname.vercel-dns.com
Agregar el Dominio en Vercel
- Ve a la configuración de tu proyecto
- Navega a "Domains"
- Ingresa tu nombre de dominio
- Vercel verifica la configuración DNS
- El certificado SSL se aprovisiona automáticamente
Certificado SSL
Vercel maneja SSL automáticamente usando Let's Encrypt. En minutos después de agregar tu dominio, HTTPS está funcionando. Sin configuración necesaria, sin certificados que renovar, sin dolores de cabeza.
Redirección WWW
Decide si tu URL canónica es example.com o www.example.com. Vercel te permite establecer una como principal y redirige automáticamente la otra. La mayoría de los sitios modernos usan el dominio sin www.
Headers de Seguridad
Los headers de seguridad le dicen a los navegadores cómo comportarse al cargar tu sitio. Son tu primera línea de defensa contra ataques comunes. Configúralos en next.config.js:
// next.config.js
const securityHeaders = [
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;"
},
{
key: 'X-Frame-Options',
value: 'DENY'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'Referrer-Policy',
value: 'origin-when-cross-origin'
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()'
}
];
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: securityHeaders,
},
];
},
};
export default nextConfig;
Esto es lo que hace cada header:
- Content-Security-Policy (CSP): Controla qué recursos puede cargar el navegador. Previene ataques XSS restringiendo de dónde pueden venir los scripts.
- X-Frame-Options: Impide que tu sitio sea embebido en iframes, bloqueando ataques de clickjacking.
- X-Content-Type-Options: Evita que los navegadores adivinen los tipos de contenido, previniendo ataques de MIME-type sniffing.
- Strict-Transport-Security (HSTS): Fuerza a los navegadores a usar siempre HTTPS, incluso si alguien escribe
http://. - Permissions-Policy: Restringe qué funcionalidades del navegador puede acceder tu sitio (cámara, micrófono, geolocalización).
No los memorices. Pídele a tu IA "agrega headers de seguridad de producción a mi configuración de Next.js" y revisa lo que genera. Lo clave es saber que existen y que importan.
Gestión de Variables de Entorno
Las variables de entorno son el puente entre tu código y el mundo exterior — claves de API, URLs de base de datos, feature flags y configuración que cambia entre entornos.
Los Tres Entornos
Desarrollo (.env.local):
DATABASE_URL="postgresql://localhost:5432/myapp_dev"
STRIPE_SECRET_KEY="sk_test_..."
NEXT_PUBLIC_APP_URL="http://localhost:3000"
Staging (Vercel Preview): Se configuran en el dashboard de Vercel con el entorno "Preview" seleccionado. Se usan para los despliegues de vista previa de pull requests.
Producción (Vercel Production): Se configuran en el dashboard de Vercel con el entorno "Production" seleccionado. Estas son tus claves de API reales y URLs de base de datos de producción.
Las Reglas de Oro
- Nunca hagas commit de secretos. Tu
.env.localdebe estar en.gitignore. Siempre. - Usa el prefijo
NEXT_PUBLIC_solo para variables que sean seguras de exponer al navegador. Las URLs de base de datos y secretos de API nunca deben tener este prefijo. - Rota los secretos regularmente. Si una clave se ve comprometida, deberías poder rotarla sin un despliegue de código — solo actualiza la variable de entorno.
- Documenta tus variables. Crea un archivo
.env.examplecon valores de ejemplo para que nuevos miembros del equipo sepan qué variables se necesitan:
# .env.example
DATABASE_URL="postgresql://user:password@host:5432/dbname"
STRIPE_SECRET_KEY="sk_test_..."
NEXT_PUBLIC_APP_URL="http://localhost:3000"
Monitoreo y Observabilidad
Hacer deploy no es la línea de meta — es la línea de salida. Una vez que tu app está en vivo, necesitas saber qué está pasando.
Rastreo de Errores con Sentry
Sentry captura errores en tiempo real y te da stack traces, contexto del usuario y la secuencia exacta de eventos que llevaron al fallo.
npx @sentry/wizard@latest -i nextjs
Este asistente configura todo: el SDK, los source maps y el archivo de configuración de Sentry. Después de la configuración, cada error no manejado en tu aplicación — del lado del cliente o del servidor — se reporta a tu dashboard de Sentry con contexto completo.
El prompt para configurarlo:
Integra el monitoreo de errores de Sentry en mi proyecto Next.js.
Configura el rastreo de errores tanto del cliente como del servidor.
Incluye source maps para stack traces legibles.
Vercel Analytics
Si estás en Vercel, su analítica integrada te da:
- Web Vitals: Datos reales de Core Web Vitals de usuarios (LCP, FID, CLS)
- Vistas de página y visitantes: Analítica básica de tráfico
- Speed Insights: Datos de rendimiento desglosados por página y dispositivo
Actívalo desde el dashboard de Vercel o instala el paquete:
npm install @vercel/analytics
// app/layout.tsx
import { Analytics } from '@vercel/analytics/react';
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Analytics />
</body>
</html>
);
}
Monitoreo de Disponibilidad
Los monitores de uptime hacen ping a tu sitio a intervalos regulares y te alertan si se cae. Las opciones incluyen:
- Better Uptime — páginas de estado atractivas, plan gratuito generoso
- UptimeRobot — simple, confiable, gratuito para hasta 50 monitores
- Checkly — más avanzado, soporta monitoreo de API y verificaciones con navegador
Configura al menos un monitor para tu página principal y otro para tu endpoint de API más crítico. Configura las alertas para que lleguen a tu teléfono (SMS o notificaciones push), no solo por email.
Gestión de Logs
Para aplicaciones simples, los logs integrados de funciones de Vercel son suficientes. Puedes verlos en el dashboard de Vercel en "Logs." Para necesidades más complejas, servicios como Axiom (que se integra directamente con Vercel), Datadog o LogTail te ofrecen gestión de logs con búsqueda y filtrado.
Optimización del Rendimiento
El rendimiento no es un lujo — es una funcionalidad. Cada 100ms de mejora en el tiempo de carga aumenta las tasas de conversión. Google usa la velocidad de página como factor de posicionamiento. Y los usuarios en conexiones lentas se irán si tu sitio no carga rápido.
Optimización de Imágenes
Next.js tiene un componente Image integrado que maneja la optimización automáticamente:
import Image from 'next/image';
export function Hero() {
return (
<Image
src="/hero.jpg"
alt="Imagen hero del producto"
width={1200}
height={630}
priority // Cargar esta imagen inmediatamente (above the fold)
sizes="(max-width: 768px) 100vw, 1200px"
/>
);
}
El componente Image automáticamente sirve formatos WebP o AVIF, genera tamaños responsivos, hace lazy-load de imágenes debajo del fold y previene el cambio de diseño al reservar espacio. Usa la prop priority solo para imágenes above the fold (imágenes hero, logos).
Optimización de Fuentes
// app/layout.tsx
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap', // Mostrar fuente de respaldo mientras carga
});
export default function RootLayout({ children }) {
return (
<html className={inter.className}>
<body>{children}</body>
</html>
);
}
next/font auto-hospeda las fuentes y elimina la petición de red externa a Google Fonts. La propiedad display: 'swap' asegura que el texto sea visible inmediatamente con una fuente de respaldo mientras la fuente personalizada carga.
División de Código
Next.js automáticamente divide el código por ruta, así cada página solo carga el JavaScript que necesita. Para componentes pesados dentro de una página, usa importaciones dinámicas:
import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('@/components/Chart'), {
loading: () => <div className="h-64 animate-pulse bg-gray-200 rounded" />,
ssr: false, // No renderizar en el servidor si es solo para el cliente
});
Esto significa que el código del componente Chart solo se descarga cuando un usuario visita una página que lo usa, no en cada carga de página.
Estrategias de Caché
- ISR (Incremental Static Regeneration): Páginas estáticas que se revalidan en un temporizador. Ideal para contenido que cambia cada hora o diariamente.
- SWR (Stale-While-Revalidate): Muestra datos cacheados inmediatamente mientras obtiene datos frescos en segundo plano. Perfecto para dashboards.
- Headers de Caché CDN: Configura headers
Cache-Controlpara cachear activos estáticos en el edge del CDN.
// app/blog/[slug]/page.tsx
export const revalidate = 3600; // Revalidar cada hora
Core Web Vitals
Google mide tres métricas clave:
- LCP (Largest Contentful Paint): Qué tan rápido carga el contenido principal. Objetivo: menos de 2.5 segundos. Solución: optimizar imágenes, usar CDN y reducir el tiempo de respuesta del servidor.
- FID (First Input Delay) / INP (Interaction to Next Paint): Qué tan rápido tu sitio responde a la interacción del usuario. Objetivo: menos de 200ms. Solución: reducir JavaScript, usar code splitting y evitar tareas largas.
- CLS (Cumulative Layout Shift): Cuánto se mueve el diseño de la página. Objetivo: menos de 0.1. Solución: establecer dimensiones de imágenes, reservar espacio para contenido dinámico y evitar elementos de carga tardía que empujen el contenido.
Base de Datos en Producción
Si has estado desarrollando con SQLite (común para desarrollo local), producción generalmente significa migrar a PostgreSQL o MySQL.
Migrando a PostgreSQL
Para un proyecto basado en Prisma, la migración es directa:
- Actualiza tu schema: Cambia el provider en
schema.prisma:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
-
Configura una base de datos PostgreSQL: Servicios como Neon, Supabase o Railway ofrecen planes gratuitos con PostgreSQL administrado.
-
Actualiza tu cadena de conexión:
DATABASE_URL="postgresql://user:password@host:5432/myapp?sslmode=require"
- Ejecuta las migraciones:
npx prisma migrate deploy
Buenas Prácticas de Base de Datos en Producción
- Backups: Asegúrate de que tu proveedor de base de datos tenga backups diarios automatizados. Prueba restaurar desde un backup al menos una vez.
- Connection Pooling: Las funciones serverless abren muchas conexiones de corta duración. Usa un pooler de conexiones como PgBouncer (integrado en Neon y Supabase) para evitar sobrecargar tu base de datos.
- Migraciones: Siempre usa una herramienta de migración (Prisma Migrate, Drizzle Kit). Nunca modifiques bases de datos de producción a mano.
- Monitoreo: Vigila las consultas lentas, el conteo de conexiones y el uso de disco. La mayoría de los proveedores de bases de datos administradas incluyen un dashboard para esto.
Lo Que Viene
Tu aplicación está desplegada, monitoreada y rindiendo bien. Pero nadie sabe que existe todavía. En la siguiente lección, abordaremos SEO, analítica y crecimiento — hacer que tu aplicación sea descubrible, rastrear lo que importa y construir una audiencia sin presupuesto de marketing. El tráfico orgánico es el mejor tipo de tráfico: gratuito, se acumula con el tiempo y está lleno de personas que buscan activamente lo que has construido.