Tema
Despliegue
Tu app está lista; ahora hay que publicarla. Next.js ofrece varias formas según el presupuesto y la infraestructura de tu cliente: Vercel (lo más sencillo), un VPS con Node (PM2 + Nginx, control total y precio fijo) o una exportación estática para hosting compartido. Aprenderás las tres, con sus ventajas y limitaciones.
Concepto
| Opción | Funciona todo (SSR, ISR, Server Actions, imágenes) | Dificultad | Costo típico |
|---|---|---|---|
| Vercel | ✅ | Muy fácil | Gratis para proyectos personales; plan Pro para comerciales |
| Netlify / Railway / Render | ✅ (con adaptadores) | Fácil | Variable |
VPS con Node (next start o standalone) | ✅ | Media | Fijo, desde ~5 USD/mes |
| Docker | ✅ | Media | Según el servidor |
Exportación estática (output: 'export') | ❌ Sólo páginas estáticas | Fácil | Cualquier hosting, incluso compartido |
Pregunta clave: ¿la app necesita un servidor? Si usa Server Actions, Route Handlers, SSR, ISR, autenticación o base de datos: sí. Si es un sitio informativo (landing, portafolio, blog sin panel), puede ser estático.
Antes de desplegar
bash
npm run build # debe terminar sin errores de TypeScript ni ESLint
npm start # prueba el build en http://localhost:3000Checklist:
- Variables de entorno de producción listas (base de datos,
AUTH_SECRET, URLs). metadataBaseyNEXT_PUBLIC_SITE_URLcon el dominio real.- Migraciones de base de datos listas (
prisma migrate deploy). - Revisa la tabla de rutas del build: ¿las que esperabas estáticas (○) lo son?
Opción 1: Vercel
Vercel es la empresa creada por los autores de Next.js: todo funciona sin configuración.
- Sube el proyecto a GitHub.
- En vercel.com → Add New → Project → importa el repositorio.
- Vercel detecta Next.js. En Environment Variables agrega
DATABASE_URL,AUTH_SECRET, etc. - Deploy.
Desde entonces:
- Cada
git pushamainpublica en producción. - Cada rama o Pull Request obtiene una URL de vista previa para que el cliente revise antes de publicar.
- HTTPS, CDN global y optimización de imágenes incluidos.
Conecta el dominio del cliente en Settings → Domains (Vercel te indica qué registros DNS crear).
bash
# Con la CLI
npm i -g vercel
vercel # vista previa
vercel --prod # producción
vercel env pull # descarga las variables a .env.localPlan gratuito
El plan Hobby de Vercel es sólo para uso no comercial. Para el sitio de un cliente usa el plan Pro (o elige otra opción de esta lección). Revisa también los límites de funciones y ancho de banda.
Para base de datos con Vercel, usa una base en la nube (Neon, Supabase, Prisma Postgres) con connection pooling, porque cada función serverless abre sus propias conexiones.
Opción 2: VPS con Node, PM2 y Nginx
Control total, precio fijo y puedes alojar varios proyectos en el mismo servidor. Arquitectura:
txt
Internet ──► Nginx (443, HTTPS) ──► Next.js con PM2 (:3000) ──► PostgreSQLModo standalone (recomendado)
Con output: 'standalone', Next genera una carpeta autocontenida con sólo los archivos y dependencias necesarios: más ligera y rápida de desplegar.
ts
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
output: 'standalone'
}
export default nextConfigbash
npm run build
# Genera .next/standalone/ con server.js
# Hay que copiar los estáticos junto a él:
cp -r public .next/standalone/
cp -r .next/static .next/standalone/.next/
node .next/standalone/server.js # prueba local en :3000Paso a paso en el servidor (Ubuntu 24.04)
bash
# 1. Preparar el servidor (como usuario no root con sudo)
sudo apt update && sudo apt upgrade -y
sudo apt install -y git nginx ufw
sudo ufw allow OpenSSH && sudo ufw allow 'Nginx Full' && sudo ufw enable
# 2. Node con nvm y PM2
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
source ~/.bashrc
nvm install 22
npm i -g pm2
# 3. Código y dependencias
git clone https://github.com/tu-usuario/mi-sitio.git ~/mi-sitio
cd ~/mi-sitio
nano .env.production # variables de producción (sólo existe en el servidor)
npm ci
npx prisma migrate deploy # si usas Prisma
npm run build
cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/js
// ecosystem.config.cjs
module.exports = {
apps: [
{
name: 'mi-sitio',
cwd: '/home/deploy/mi-sitio',
script: '.next/standalone/server.js',
env: {
NODE_ENV: 'production',
PORT: 3000,
HOSTNAME: '127.0.0.1' // sólo accesible desde Nginx
},
max_memory_restart: '500M'
}
]
}Variables de entorno en standalone
server.js lee las variables del entorno del proceso. Puedes ponerlas en env del ecosystem, o cargarlas con node_args: '--env-file=.env.production'. Recuerda que las NEXT_PUBLIC_ se incrustan en el build: deben existir al ejecutar npm run build.
bash
pm2 start ecosystem.config.cjs
pm2 save
pm2 startup # ejecuta el comando que imprime para arrancar al reiniciarNginx como proxy inverso
nginx
# /etc/nginx/sites-available/mi-sitio
server {
listen 80;
server_name misitio.com www.misitio.com;
client_max_body_size 10M;
# Archivos estáticos con hash: caché de un año
location /_next/static/ {
alias /home/deploy/mi-sitio/.next/standalone/.next/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}bash
sudo ln -s /etc/nginx/sites-available/mi-sitio /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
# HTTPS gratis
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d misitio.com -d www.misitio.comScript de actualización
bash
#!/usr/bin/env bash
# deploy.sh
set -e
cd ~/mi-sitio
git pull origin main
npm ci
npx prisma migrate deploy
npm run build
cp -r public .next/standalone/
cp -r .next/static .next/standalone/.next/
pm2 reload mi-sitio
echo "✅ Desplegado $(git rev-parse --short HEAD)"Más detalles sobre seguridad del servidor, PM2 y Nginx en Despliegue de Node y en Servidores.
ISR y caché en VPS
En un servidor propio, la caché de ISR se guarda en disco (.next/cache). Si ejecutas varias instancias (por ejemplo, en modo clúster o varios servidores), cada una tendría su propia caché; para eso Next.js permite configurar un cacheHandler compartido (por ejemplo, con Redis). Con una sola instancia no necesitas nada extra.
Docker (opcional)
dockerfile
# Dockerfile
FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npx prisma generate && npm run build
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production PORT=3000 HOSTNAME=0.0.0.0
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static
COPY --from=build /app/public ./public
USER node
EXPOSE 3000
CMD ["node", "server.js"]Opción 3: exportación estática (hosting compartido)
Muchos clientes ya tienen un hosting compartido con cPanel (Apache + PHP) sin Node. Si el sitio no necesita servidor, Next.js puede generar HTML, CSS y JS puros.
ts
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
output: 'export',
trailingSlash: true, // genera /nosotros/index.html (funciona mejor en Apache)
images: { unoptimized: true } // no hay servidor que optimice imágenes
}
export default nextConfigbash
npm run build # genera la carpeta out/Sube el contenido de out/ a public_html/ por FTP o con el administrador de archivos.
Qué funciona y qué no
| ✅ Funciona | ❌ No funciona |
|---|---|
| Server Components que obtienen datos en el build | Server Actions |
Rutas dinámicas con generateStaticParams | Route Handlers dinámicos (sólo GET estáticos) |
| Client Components, hooks, interactividad | cookies(), headers(), SSR por petición |
| Metadata, sitemap, robots | ISR / revalidate |
next/font | Middleware / proxy |
next/image con unoptimized | Optimización de imágenes |
Para formularios de contacto en un sitio estático, usa un servicio externo (Formspree, Web3Forms, Resend con una función) o un pequeño script PHP en el mismo hosting.
.htaccess para Apache
apache
# public/.htaccess (se copia a out/ en el build)
ErrorDocument 404 /404.html
<IfModule mod_rewrite.c>
RewriteEngine On
# Forzar HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>
<IfModule mod_headers.c>
<FilesMatch "\.(js|css|woff2|png|jpg|webp|svg)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
</IfModule>Con trailingSlash: true, cada ruta es una carpeta con su index.html, así que Apache las sirve sin reglas adicionales.
Actualizar el contenido
Como todo se genera en el build, para publicar un cambio (un nuevo post) hay que volver a ejecutar npm run build y subir out/. Automatízalo con GitHub Actions + FTP (igual que en Despliegue de React), o usa un CMS que dispare el build.
Monitoreo después de publicar
- Errores: Sentry (
npx @sentry/wizard@latest -i nextjs). - Analítica: Vercel Analytics, Plausible o Google Analytics.
- Disponibilidad: UptimeRobot o Better Stack revisando la página principal.
- SEO: da de alta el sitio en Google Search Console y envía el sitemap.
Error común
Olvidar que las variables NEXT_PUBLIC_ se incrustan al hacer el build. Si cambias una en Vercel o en el servidor, debes volver a construir la app para que surta efecto.
Ejercicios
- Despliega en Vercel la app del blog de la lección 5 y crea una rama con un cambio para obtener una URL de vista previa.
- Convierte una landing page (sin Server Actions) a exportación estática, genera
out/y pruébala localmente connpx serve out.
Ver solución
- Sube el proyecto a GitHub, impórtalo en Vercel y despliega. Luego:
bash
git checkout -b nuevo-diseno
# haz un cambio visible
git commit -am "Prueba de diseño"
git push -u origin nuevo-disenoVercel comenta en el Pull Request (o muestra en el panel) la URL de vista previa de esa rama.
ts
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
output: 'export',
trailingSlash: true,
images: { unoptimized: true }
}
export default nextConfigbash
npm run build # si alguna página usa algo no soportado, el build te dirá cuál
npx serve out # http://localhost:3000Después sube el contenido de out/ a public_html/ de tu hosting.
Resumen
- Vercel: conecta GitHub y listo; vistas previas por rama. Plan Pro para proyectos comerciales.
- VPS:
output: 'standalone'+ PM2 + Nginx + Certbot; control total y precio fijo. - Exportación estática (
output: 'export'): subeout/a cualquier hosting, pero sin Server Actions, SSR, ISR ni middleware. - Las variables
NEXT_PUBLIC_se fijan en el build; monitorea errores, disponibilidad y SEO tras publicar.