Next.js genera páginas en el servidor, así que en producción necesita un proceso de Node.js en marcha además de los archivos estáticos. En este tutorial desplegarás una aplicación Next.js en Ubuntu 24.04: instalarás Node.js 24 desde el repositorio oficial de NodeSource, compilarás la aplicación, la ejecutarás con next start como servicio de systemd y pondrás Nginx delante para servir los estáticos con caché larga y gestionar HTTPS.
Requisitos previos
Para seguir esta guía necesitas:
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 2 GB de RAM. La compilación con
next buildes la parte que más memoria consume. - Un usuario no root con privilegios
sudo. En los ejemplos se llamayour_usery es también el usuario que ejecutará la aplicación. - Un dominio con un registro DNS
Aapuntando a la IP del servidor. En los ejemplos se usayour_domain. - Una aplicación Next.js en un repositorio Git, o las ganas de probar con un proyecto nuevo.
La aplicación de ejemplo se llama myapp y vive en /var/www/myapp.
Paso 1: Instalar Node.js 24
La versión de Node.js de los repositorios de Ubuntu 24.04 es demasiado antigua para las versiones actuales de Next.js. Instala Node.js 24 LTS desde el repositorio de NodeSource. Primero, descarga su clave de firma:
sudo apt update
sudo apt install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
Añade el repositorio e instala Node.js, que incluye npm:
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_24.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
sudo apt update
sudo apt install nodejs
Comprueba las versiones:
node -v
npm -v
v24.x.x
11.x.x
Instala también Nginx y Git, y abre en el cortafuegos SSH, HTTP y HTTPS:
sudo apt install nginx git
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
Paso 2: Descargar y compilar la aplicación
Crea el directorio de la aplicación con tu usuario como propietario:
sudo mkdir -p /var/www/myapp
sudo chown your_user:your_user /var/www/myapp
Si tienes la aplicación en un repositorio, clónala:
git clone https://github.com/your_account/myapp.git /var/www/myapp
Si solo quieres probar el despliegue, crea un proyecto nuevo en ese directorio y acepta las opciones por defecto del asistente:
npx create-next-app@latest /var/www/myapp
Si la aplicación usa variables de entorno en el servidor (claves de API, URL de la base de datos), guárdalas en .env.production.local, que Next.js carga en producción y que no se sube al repositorio:
nano /var/www/myapp/.env.production.local
DATABASE_URL=postgres://user:your_strong_password@localhost:5432/myapp
Importantelas variables con prefijo
NEXT_PUBLIC_se incrustan en el JavaScript del navegador durantenext build. Si cambias alguna, tienes que volver a compilar; y nunca guardes secretos en ellas.
Instala las dependencias exactamente como están en package-lock.json y compila. npm ci instala también las dependencias de desarrollo, que hacen falta para compilar (TypeScript, por ejemplo):
cd /var/www/myapp
npm ci
npm run build
Next.js 16.x.x
Creating an optimized production build ...
Compiled successfully
...
La compilación genera el directorio .next, que contiene el servidor compilado y los estáticos en .next/static.
Paso 3: Ejecutar Next.js con systemd
Crea un servicio de systemd que ejecute next start y lo reinicie si falla. Las opciones -H 127.0.0.1 -p 3000 hacen que solo escuche en local, de modo que únicamente Nginx puede llegar a él:
sudo nano /etc/systemd/system/myapp.service
[Unit]
Description=myapp Next.js server
After=network.target
[Service]
Type=simple
User=your_user
WorkingDirectory=/var/www/myapp
Environment=NODE_ENV=production
ExecStart=/usr/bin/npm run start -- -H 127.0.0.1 -p 3000
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
El script start del package.json de un proyecto Next.js es next start, y -- le pasa el resto de argumentos. Activa e inicia el servicio:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
systemctl status myapp --no-pager
● myapp.service - myapp Next.js server
Active: active (running) since ...
Comprueba que responde:
curl -I http://127.0.0.1:3000
HTTP/1.1 200 OK
X-Powered-By: Next.js
Content-Type: text/html; charset=utf-8
...
Los logs de la aplicación, incluidos los console.log del servidor, están en journalctl -u myapp.
Paso 4: Configurar Nginx con caché para los estáticos
Los archivos de /_next/static/ llevan un hash en el nombre, así que nunca cambian y el navegador puede guardarlos un año. Nginx los servirá directamente desde disco, sin pasar por Node.js, y enviará el resto de peticiones a la aplicación. Crea el bloque de servidor:
sudo nano /etc/nginx/sites-available/myapp
server {
listen 80;
listen [::]:80;
server_name your_domain www.your_domain;
location /_next/static/ {
alias /var/www/myapp/.next/static/;
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
include proxy_params;
}
}
El archivo proxy_params de Ubuntu añade las cabeceras Host, X-Real-IP, X-Forwarded-For y X-Forwarded-Proto, que Next.js usa para construir URL absolutas y en los redirects.
Activa el sitio, desactiva el sitio por defecto y comprueba la sintaxis:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
Comprueba que los estáticos salen de Nginx con la cabecera de caché. Toma el nombre de un archivo JavaScript de la página principal:
curl -s http://your_domain | grep -o '/_next/static/[^"]*\.js' | head -1
/_next/static/chunks/0a1b2c3d4e5f6a7b.js
Y pide ese archivo, sustituyendo la ruta por la que obtuviste:
curl -I http://your_domain/_next/static/chunks/0a1b2c3d4e5f6a7b.js
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Content-Type: application/javascript
Cache-Control: public, max-age=31536000, immutable
Paso 5: Activar HTTPS con Let's Encrypt
Instala Certbot con su plugin para Nginx y solicita el certificado. Certbot añade la configuración TLS y la redirección de HTTP a HTTPS:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your_domain -d www.your_domain
Comprueba que la renovación automática funciona:
sudo certbot renew --dry-run
Abre https://your_domain en el navegador y deberías ver tu aplicación servida por HTTPS.
Paso 6: Ajustes de rendimiento
Con esta configuración, Next.js ya aplica las optimizaciones principales: prerenderiza las páginas estáticas durante la compilación, comprime las respuestas con gzip y optimiza las imágenes de next/image con sharp. Hay dos ajustes que merece la pena revisar en un servidor propio.
Caché de imágenes y de ISR. Las imágenes optimizadas y las páginas regeneradas con ISR se guardan en .next/cache. El servicio se ejecuta con tu usuario, propietario del directorio, así que puede escribir en él sin cambiar permisos. Comprueba que se llena tras visitar páginas con imágenes:
du -sh /var/www/myapp/.next/cache
Comprimir en Nginx en vez de en Node.js. Si prefieres que la compresión la haga Nginx, desactívala en Next.js añadiendo compress: false a next.config.js (o next.config.ts):
const nextConfig = {
compress: false,
};
Después activa gzip para los tipos de texto en /etc/nginx/nginx.conf (Ubuntu ya trae gzip on; en el bloque http, y gzip_types y las demás directivas comentadas), vuelve a compilar y reinicia el servicio.
Actualizar la aplicación
Para desplegar cambios, descarga el código, instala dependencias, compila y reinicia el servicio:
cd /var/www/myapp
git pull
npm ci
npm run build
sudo systemctl restart myapp
Durante npm run build el directorio .next se reemplaza mientras el servidor sigue en marcha, y algunas peticiones pueden fallar durante unos segundos. Si necesitas despliegues sin cortes, compila en un directorio nuevo y cambia un enlace simbólico al terminar, o compila en un pipeline de CI.
Solución de problemas
JavaScript heap out of memoryo el proceso muere al compilar: el servidor no tiene memoria suficiente paranext build. Añade swap o compila en una máquina con más RAM.- 502 Bad Gateway: el servicio no está en marcha. Revisa
sudo journalctl -u myapp -n 50; si vesCould not find a production build, ejecutanpm run buildantes de arrancar. - 404 en archivos de
/_next/static/: la ruta dealiasno coincide con el directorio real o falta la barra final enaliaso enlocation. - Una variable
NEXT_PUBLIC_no cambia: se incrusta en la compilación. Vuelve a ejecutarnpm run buildy reinicia el servicio.
Conclusión
Tu aplicación Next.js se ejecuta como servicio de systemd con Node.js 24, escucha solo en local y Nginx la publica por HTTPS sirviendo los estáticos con caché de un año. Como siguientes pasos puedes:
- Usar
output: "standalone"ennext.config.jspara generar un servidor mínimo que no necesitenode_modulescompleto en producción. - Compilar en un pipeline de CI y copiar solo el resultado al servidor para evitar cortes durante el despliegue.
- Poner una CDN delante de
/_next/static/si tienes tráfico de varias regiones.
