Next.js
Despliega una app Next.js en Skiffly con Railpack — App Router o Pages, `next start` o salida standalone, `NEXT_PUBLIC_*` en tiempo de build, Postgres con Prisma o Drizzle, optimización de imágenes y notas sobre caché.
Para Railpack, un repositorio Next.js es un proyecto de Node: instala con el gestor de paquetes que implica tu lockfile, ejecuta npm run build (next build) y arranca con npm start (next start). No hay que configurar nada para un create-next-app recién creado. La página de Node.js cubre versiones, gestores de paquetes y el comportamiento general de Node; esta trata de lo que es específico de Next.js.
Puerto#
next start lee PORT del entorno, así que la app escucha donde Skiffly espera (el puerto del servicio; 8080 si no está definido). No pongas -p 3000 fijo en el script start a menos que también configures el puerto del servicio en 3000. next dev no es un servidor de producción; mantén start: next start.
Modos de renderizado#
next.config.* | Qué hace Railpack | Notas |
|---|---|---|
| por defecto (servidor) | imagen de Node, next start | SSR, route handlers, server actions e ISR funcionan; la caché de ISR vive en el disco del contenedor y es por réplica |
output: "standalone" | el mismo build; configura el comando de inicio en node .next/standalone/server.js | imagen más pequeña; copia public y .next/static tú mismo en el comando de build, p. ej. next build && cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/ |
output: "export" | se detecta como sitio estático: el directorio exportado out/ lo sirve Caddy | sin funciones de servidor; consulta Sitios estáticos |
Con la salida standalone, el servidor también lee PORT y se enlaza a 0.0.0.0 por defecto (define HOSTNAME=0.0.0.0 en versiones antiguas).
Variables de entorno en tiempo de build#
Los valores NEXT_PUBLIC_* se incrustan durante next build. Skiffly pasa todas las variables del servicio al build de Railpack, así que defínelas como variables normales del servicio antes de desplegar; cambiar una más tarde requiere un nuevo build (skiffly up, Desplegar en el panel, MCP deploy), no un reinicio. Los secretos exclusivos del servidor (DATABASE_URL, AUTH_SECRET) se leen en tiempo de ejecución y no necesitan un nuevo build.
Variables útiles en tiempo de ejecución:
NEXT_TELEMETRY_DISABLED=1
AUTH_URL=https://${{self.SKIFFLY_PUBLIC_DOMAIN}} # URL canónica de NextAuth / Auth.jsVincula la lista de hosts de confianza (AUTH_TRUST_HOST=true, serverActions.allowedOrigins) a tu dominio propio en cuanto agregues uno.
Base de datos#
skiffly deploy --template postgres
skiffly variables set 'DATABASE_URL=${{Postgres.DATABASE_URL}}'- Prisma: mantén
prisma generateenpostinstall(o"build": "prisma generate && next build"). Migra desde el comando de inicio:sh -c "npx prisma migrate deploy && next start", o como comando puntual conskiffly ssh -- npx prisma migrate deploy. No hay fase de release. - Drizzle:
drizzle-kit migrateen el mismo lugar;drizzle-kit pushestá bien para prototipos. - Pool de conexiones: una réplica de Next.js abre un puñado de conexiones; con varias réplicas y route handlers al estilo serverless, un pooler (plantilla
pgbouncer) mantiene tranquilo a Postgres.
La base de datos solo es alcanzable en la red privada; next build no debe consultarla (evita llamadas a la base de datos en el nivel superior de páginas generadas estáticamente, o márcalas con dynamic = "force-dynamic").
Imágenes, fuentes y caché#
- La optimización de
next/imagecorre en el proceso de Node;sharplo instala el propio Next.js (npm i sharpen versiones antiguas). Permite los hosts remotos enimages.remotePatterns. next/fontdescarga las fuentes de Google en tiempo de build; el build tiene acceso a internet, así que funciona.- La caché de
fetchy la de ISR se guardan en.next/cache, en el disco efímero del contenedor: al volver a desplegar arranca vacía, y las réplicas no la comparten. Para una caché compartida usa uncacheHandlerpersonalizado respaldado por Redis (skiffly deploy --template redis).
Health check#
Configura la ruta del health check en / o en un route handler ligero (app/healthz/route.ts que devuelva Response.json({ ok: true })). Evita páginas que consulten la base de datos en cada sonda.
Monorepos (Turborepo, pnpm workspaces)#
Configura el directorio raíz del servicio en la app (apps/web) solo cuando la app sea autocontenida. Cuando importa paquetes del workspace, deja la raíz en el repositorio y define explícitamente los comandos de build y de inicio:
build: pnpm install --frozen-lockfile && pnpm --filter web build
start: pnpm --filter web startRailpack detecta pnpm-workspace.yaml e instala desde la raíz; next.config con transpilePackages o outputFileTracingRoot se encarga del resto. El tutorial de monorepo recorre una configuración completa.
Problemas comunes#
| Síntoma | Solución |
|---|---|
Despliegue FAILED, el log muestra Ready on http://localhost:3000 pero no llega tráfico | -p 3000 fijo en start mientras el puerto del servicio es 8080: quita el flag o configura el puerto en 3000 |
NEXT_PUBLIC_API_URL es undefined en el navegador | la variable se agregó después del build; lanza un nuevo build |
Error: Cannot find module 'sharp' | agrega sharp a las dependencias |
Build terminado a la fuerza (Killed, exit 137) durante next build | baja experimental.cpus en next.config o define NODE_OPTIONS=--max-old-space-size=2048 como variable del servicio (llega al build) |
Invalid src prop … hostname not configured | agrega el host a images.remotePatterns |
| Las server actions fallan detrás de un dominio propio | AUTH_TRUST_HOST=true / experimental.serverActions.allowedOrigins |
Ejemplo#
import { defineSkiffly, generate, github, postgres, project, service } from "@skiffly/config";
export default defineSkiffly(() => {
const db = postgres("db");
const web = service("web", {
source: github("acme/shop"),
start: "sh -c 'npx prisma migrate deploy && next start'",
healthcheck: "/healthz",
port: 3000,
env: { NODE_ENV: "production", DATABASE_URL: db.env.DATABASE_URL, AUTH_SECRET: generate("hex32"), AUTH_URL: "https://${{self.SKIFFLY_PUBLIC_DOMAIN}}" },
domain: true,
});
return project("shop", { resources: [db, web] });
});Paso a paso: tutorial de Next.js + Postgres. Landing: /deploy/nextjs.