Deploy de Next.js 15 en Railway sin Vercel: guía paso a paso
Vercel es excelente, pero tiene un techo de precio que duele cuando eres una startup latinoamericana escalando. Railway te da un servidor real con PostgreSQL, Redis y cron jobs en el mismo proyecto, por mucho menos.
¿Por qué no Vercel?
Vercel es la plataforma canónica para Next.js: DX impecable, previews automáticos, Edge Network global. Para un proyecto personal o una landing, está bien. El problema llega cuando comienzas a necesitar más:
- Background jobs: en el plan gratuito las funciones cortan a los 10 segundos. Si usas
pg-bosso workers asincrónicos, estás fuera. - Precio en escala: Vercel Pro cuesta $20/mes por miembro del equipo, más la base de datos externa (Neon, PlanetScale, Supabase). Para un equipo de tres personas en Colombia, eso es una factura seria.
- Sin estado persistente: cada invocación de función es stateless. Conexiones a la DB se abren y cierran constantemente; sin pooling dedicado, el rendimiento cae.
Railway resuelve estos tres puntos: corre tu app como un proceso Node.js de larga duración, incluye PostgreSQL y Redis en el mismo proyecto, y cobra por uso real (no por asiento).
Qué ofrece Railway
Railway es una plataforma PaaS basada en contenedores (no serverless). Cuando haces deploy de tu Next.js ahí, Railway construye una imagen Docker con Nixpacks y levanta un contenedor que corre continuamente.
Contenedores persistentes
El proceso Node no se apaga entre requests. Conexiones a DB reutilizables.
PostgreSQL y Redis integrados
Un click y tienes la DB en el mismo proyecto. Railway inyecta DATABASE_URL automáticamente.
Cron Jobs nativos
Sin costo adicional. Define el schedule directamente en el dashboard.
Precio justo
$5 de crédito gratis al mes. Después: $0.000463/vCPU-hora y $0.000231/GB-hora de RAM.
Paso 1 — Crear el proyecto en Railway
- 1Entra a railway.app y crea una cuenta con GitHub.
- 2Click en New Project → Deploy from GitHub repo. Autoriza el repositorio de tu app.
- 3Railway detecta Next.js automáticamente vía Nixpacks y propone
npm run buildynpm run startcomo comandos. Puedes aceptarlos o sobreescribirlos. - 4Agrega un servicio de PostgreSQL desde el mismo proyecto: click en + New → Database → PostgreSQL. Railway vincula automáticamente la variable
DATABASE_URLal servicio de tu app.
Paso 2 — Configuración de build (monorepo)
Si tu repositorio es un monorepo (por ejemplo, Turborepo con apps/web), Railway necesita saber dónde vivé la app. Tienes dos opciones:
Opción A — railway.json en la raíz del repo
{
"$schema": "https://schema.railway.app/railway-schema.json",
"build": {
"builder": "NIXPACKS",
"buildCommand": "cd apps/web && npm install && npm run build"
},
"deploy": {
"startCommand": "cd apps/web && npm run start",
"healthcheckPath": "/",
"healthcheckTimeout": 30,
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 3
}
}Opción B — nixpacks.toml dentro de apps/web
Si prefieres que cada app del monorepo tenga su propia configuración, puedes colocar un nixpacks.toml dentro de apps/web/ y apuntar el root directory del servicio en Railway a esa carpeta:
# apps/web/nixpacks.toml
[phases.build]
cmds = ["npm run build"]
[start]
cmd = "npm run start"Para Turborepo con workspace, el comando de build puede incluir el filtro:
# Build command en Railway dashboard (Settings → Build)
npx turbo run build --filter=web
# Start command
node apps/web/.next/standalone/server.jsSi usas output: 'standalone' en next.config.ts, el servidor arranca con node .next/standalone/server.js y el build es más ligero (sin node_modules duplicados). Muy recomendado en Railway.
Paso 3 — next.config.ts recomendado para Railway
Activa el modo standalone para que Railway solo copie lo que necesita en el contenedor, y define los hostnames de imágenes remotas si las usas:
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
output: 'standalone',
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'res.cloudinary.com',
},
],
},
// Desactiva x-powered-by para no exponer la versión
poweredByHeader: false,
// Variables de entorno disponibles en el cliente
// (solo las que NO son secretas)
env: {
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL ?? '',
},
}
export default nextConfigPaso 4 — Variables de entorno en Railway
Ve a tu servicio → pestaña Variables. Agrega tus variables una a una o importa un .env completo con el botón RAW Editor.
# Railway inyecta esto automáticamente cuando tienes PostgreSQL en el mismo proyecto:
DATABASE_URL=postgresql://postgres:PASSWORD@HOST:5432/railway
# Las tuyas van aquí (ejemplo):
NEXT_PUBLIC_APP_URL=https://tudominio.com
OPENAI_API_KEY=sk-...
JWT_SECRET=un-secreto-largo-y-aleatorio
NODE_ENV=productionRailway vincula los servicios del mismo proyecto con variables de referencia. Si agregas Redis, la variable REDIS_URL también se inyecta automáticamente — no tienes que copiar y pegar URLs entre servicios.
Si cambias una variable de entorno, Railway hace redeploy automático. Puedes desactivar este comportamiento en Settings → Auto-Deploy si prefieres controlar cuándo se aplican los cambios.
La diferencia fundamental: Node.js server vs serverless
En Vercel, cada route handler es una función Lambda. Se levanta, responde y muere. En Railway, tu app corre como un proceso next start que vive continuamente. Esto cambia todo:
| Característica | Railway (Node) | Vercel (Serverless) |
|---|---|---|
| Timeout máximo | Ilimitado | 10s (hobby) / 300s (pro) |
| Background jobs | ✓ Sí (pg-boss, cron) | ✗ No nativo |
| Conexiones DB persistentes | ✓ Pool reutilizable | ✗ Reconecta por invocación |
| Edge Runtime | ✗ Todo es Node | ✓ Sí (con limitaciones) |
| ISR / On-demand revalidation | ✓ Funciona | ✓ Optimizado en Vercel |
| WebSockets | ✓ Sí | ✗ No en funciones |
En la práctica, en edwsystem.com corremos pg-boss para procesar leads en background, enviar emails y ejecutar integraciones con IA. Nada de eso funciona en el plan gratuito de Vercel.
Paso 5 — Dominio personalizado y SSL
Railway genera un dominio *.up.railway.app automáticamente. Para usar tu propio dominio:
- 1Ve a Settings → Networking → Custom Domain y escribe tu dominio (ej.
edwsystem.com). - 2Railway te da un registro CNAME (para subdominios) o un registro A (para el apex). Agrégalo en tu registrador (GoDaddy, Cloudflare, Namecheap, etc.).
- 3Railway emite el certificado SSL vía Let's Encrypt automáticamente una vez que el DNS propaga (generalmente menos de 5 minutos con Cloudflare).
# Ejemplo de configuración DNS en Cloudflare (modo proxy OFF para SSL de Railway)
# Tipo Nombre Contenido TTL
CNAME www myapp-production.up.railway.app Auto
A @ [IP que Railway proporciona] AutoSi usas Cloudflare como proxy (nube naranja), desactívalo temporalmente o configura el modo Full (Strict) para que Cloudflare confíe en el certificado de Railway. De lo contrario, obtendrás errores de SSL.
Paso 6 — Health checks y deploys sin downtime
Railway hace rolling deployments: levanta el nuevo contenedor, espera que el healthcheck pase, y solo entonces elimina el viejo. Configura el healthcheck en railway.json o en el dashboard:
// En railway.json
{
"deploy": {
"healthcheckPath": "/api/health",
"healthcheckTimeout": 30
}
}
// app/api/health/route.ts
export async function GET() {
return Response.json({ status: 'ok', ts: Date.now() })
}Si el nuevo deploy falla el healthcheck tres veces consecutivas, Railway revierte al contenedor anterior automáticamente. Define la política de restart:
{
"deploy": {
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 3
}
}Monitoreo: logs, métricas y alertas
Railway incluye observabilidad básica sin configuración adicional:
- Logs en tiempo real desde el dashboard o con la CLI:
railway logs --tail - Métricas de CPU y RAM con gráficas históricas en la pestaña Metrics del servicio.
- Alertas por email o Slack cuando un servicio se cae o supera el uso de RAM configurado.
Para logging estructurado en producción, usa pino con formato JSON. Railway parsea los logs JSON automáticamente y te permite filtrarlos por campo.
Comparativa de costos real
Nuestro stack en producción: Next.js 15 + PostgreSQL + Redis + pg-boss. Consumo promedio: 0.5 vCPU y 512 MB RAM por servicio.
| Ítem | Railway | Vercel Pro + Neon |
|---|---|---|
| App (Next.js) | ~$5/mes | $20/mes (por usuario) |
| PostgreSQL | ~$5/mes (incluido) | $19/mes (Neon Pro) |
| Redis | ~$2/mes (incluido) | $7/mes (Upstash) |
| Dominios / SSL | Gratis | Gratis |
| Total estimado | ~$12/mes | ~$46/mes |
Para una startup colombiana, la diferencia de ~$34/mes equivale a más de $136.000 COP al mes (a tasa de cambio de abril 2025). Con ese ahorro, cubres el dominio y algo más.
¿Cuándo seguir usando Vercel?
Railway no es mejor en todos los casos. Usa Vercel cuando:
- Tu app es puramente serverless sin necesidad de procesos de larga duración ni estado en memoria.
- Usas intensivamente ISR (Incremental Static Regeneration) y quieres el caché distribuido en el Edge de Vercel sin configurar nada extra.
- Necesitas Edge Functions reales que corran en la región más cercana al usuario (< 10ms latencia en todo el mundo).
- El equipo es grande y el valor del DX de Vercel (previews por PR, comentarios de equipo) justifica el precio.
Resumen: el flujo completo
# 1. Activa output standalone en next.config.ts
output: 'standalone'
# 2. Crea railway.json en la raíz del repo (monorepo)
# Con buildCommand, startCommand, healthcheckPath
# 3. En Railway dashboard:
# - New Project → GitHub repo
# - + New → Database → PostgreSQL (DATABASE_URL se inyecta sola)
# - Settings → Networking → Custom Domain
# - Variables → agregar secrets
# 4. Primer deploy automático en cada push a main
# Railway: build → healthcheck → swap → zero-downtime
# 5. Monitoreo: railway logs --tail | dashboard Metrics tabEn Edwsystem llevamos más de seis meses con este setup en producción. El tiempo de deploy es de ~3 minutos (build de Next.js incluido), el uptime está en 99.9% y el costo total no supera los $15/mes con tráfico moderado. Si estás evaluando opciones para tu próximo proyecto SaaS, Railway merece estar en la lista.
¿Te fue útil este artículo?
Déjame tu email y te aviso cuando publique nuevos artículos técnicos.