Saltar al contenido

Autoalojar Next.js y qué deja de funcionar

Next.js corre donde corra Node. Cuatro funciones las da la plataforma y no el framework, y saber cuáles son separa una migración tranquila de una con sorpresas.

6 min de lectura

"¿Podemos correr esto en nuestra propia infraestructura?" tiene una respuesta corta y otra larga. La corta es sí: next start es un servidor de Node y funciona en una VM, en un contenedor, detrás de tu balanceador, en tu región.

La larga es que cuatro cosas que la gente toma por funciones de Next.js las da en realidad la plataforma de hosting, y son justo las cuatro que sorprenden a los equipos una semana después de la migración.

El build que de verdad se puede desplegar

El build por defecto te deja un directorio .next que necesita al lado todo el árbol de node_modules. Para un contenedor eso son cientos de megas de dependencias que no ejecutas.

// next.config.ts
export default {
  output: 'standalone',
};

Esto rastrea los módulos que el servidor alcanza de verdad, los copia a .next/standalone y añade un server.js que arranca sin gestor de paquetes. En las bases de código que hemos movido, la imagen pasa de ~1,2 GB a ~180 MB.

FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
 
FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
 
FROM node:22-alpine AS run
WORKDIR /app
ENV NODE_ENV=production
# Los assets estáticos y public/ NO están dentro de standalone. Copiarlos es
# el paso que se olvida, y el síntoma es un sitio sin CSS.
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static
COPY --from=build /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]

Dos cosas de ese fichero conviene decirlas claras. .next/static y public/ no forman parte de la salida standalone y hay que copiarlos aparte: si te los saltas, el despliegue sirve HTML sin CSS y sin imágenes. Y npm run build corre al construir la imagen, así que toda variable de entorno que tu código lea a nivel de módulo tiene que estar entonces, no solo en tiempo de ejecución.

Las cuatro cosas que hacía la plataforma

Optimización de imágenes. next/image necesita algo que redimensione y recodifique. En una plataforma eso es un servicio; en tu servidor es sharp, en tu proceso y en tu CPU.

npm install sharp

Instálalo y se usa solo. Si tu tráfico convierte ese coste de CPU en algo real, apunta images.loader a un CDN que redimensione, o pregenera los tamaños en el build. Lo que no puedes hacer es dejar images.unoptimized: true en la configuración y olvidarlo: eso manda el fichero original a cada visitante y deshace en silencio la razón por la que usabas el componente.

ISR y la caché de revalidación. En un contenedor único funciona: la caché está en disco local. En tres contenedores detrás de un balanceador no: cada uno tiene su disco, revalidateTag limpia la caché de la instancia que recibió la llamada, y las otras dos siguen sirviendo la página vieja hasta que revaliden por su cuenta. La solución es un cache handler compartido:

// next.config.ts
export default {
  cacheHandler: require.resolve('./cache-handler.js'),
  cacheMaxMemorySize: 0, // desactiva el LRU en proceso; Redis es la fuente
};

Sirve cualquier handler con Redis detrás. Lo importante es entender por qué hace falta, porque si no, "la página se actualizó para unos usuarios y para otros no" parece un fallo imposible. Qué hace cada caché conviene tenerlo claro: las cuatro capas y cuál te ha mordido.

Middleware en el edge. Tu middleware ya no corre en cien ubicaciones antes de que la petición llegue a un servidor; corre en tu servidor, en tu única región. El código escrito asumiendo que es casi gratis -una consulta de geolocalización, una asignación A/B- está ahora en el camino crítico de cada petición. Ese coste es real y conviene medirlo en vez de suponerlo.

Streaming a través de tu proxy. Suspense y loading.tsx envían la respuesta a trozos. Un nginx delante con la configuración por defecto almacena toda la respuesta antes de mandarla, lo que convierte el streaming en una versión más lenta de no hacer streaming, y sin ningún error que te avise.

location / {
  proxy_pass http://app:3000;
  proxy_buffering off;
  proxy_http_version 1.1;
  proxy_set_header Connection "";
}

Lo que ganas

No es solo coste, aunque a cierta escala la diferencia es grande. Tienes tus datos en una jurisdicción que elegiste, que para un cliente con requisito de residencia de datos turco o europeo no es negociable. Puedes correr en la misma red que tu base de datos, lo que quita un viaje de ida y vuelta a cada consulta. Y tienes un despliegue que nadie puede cambiarte por debajo con una decisión de precios ajena.

Lo que cuesta

Ahora la disponibilidad tiene dueño. Los despliegues de vista previa por pull request, los rollbacks instantáneos y un CDN global son trabajo real de reconstruir, y un equipo que nunca ha operado infraestructura se pasará el primer mes redescubriendo por qué existen esas funciones.

Nuestra regla: si la aplicación vive en una región, si el equipo ya opera servicios, o si hay un requisito de residencia de datos, autoalojar es directamente lo correcto. Si es un sitio de marketing con dos desarrolladores y tráfico global, la plataforma sale más barata que el sueldo de quien mantiene la alternativa.

En cualquier caso, decide por esos motivos y no por un benchmark. El framework corre igual en los dos sitios: lo que se mueve son las cuatro funciones de arriba.

Volver a todos los artículos