Por que tu factura de Vercel se ha multiplicado

Dashboard de consumo cloud mostrando picos de facturación inesperados

La llamada suele llegar un martes por la mañana. Alguien acaba de abrir el panel de facturación, ha visto un número con tres ceros más de los esperados, y pregunta lo mismo: "¿esto es un error?". No lo es. Y en el 90% de los casos que hemos revisado en mvptosaas, la causa no está donde el equipo la busca.

Antes de tocar el package.json, antes de plantear una migración, conviene entender qué está sangrando exactamente. Porque un recibo de factura de Vercel se ha multiplicado no es una única enfermedad: son cuatro o cinco patologías distintas que se disfrazan con los mismos síntomas.

Los síntomas: el patrón que se repite en cada caso

Un recibo cloud multiplicado se produce cuando una o varias de las cinco métricas facturables de la plataforma (Edge Requests, Data Transfer, Function Duration, Build Time y Requests a base de datos) se disparan sin cambio equivalente en el negocio. No es un fallo de precios: es un desbordamiento con causa raíz identificable.

Hace algo más de un año revisamos, uno tras otro, siete proyectos con el mismo diagnóstico: recibo multiplicado entre 4x y 12x sin cambio aparente en el tráfico. Los siete tenían pilas técnicas distintas. Los siete presentaban el mismo cuadro clínico.

El primer síntoma es el desacoplo entre visitas humanas y consumo. Google Analytics marca 8.000 sesiones al mes, pero la plataforma reporta 2,3 millones de Edge Requests. Ahí ya no hay debate: alguien te está visitando sin ser humano.

El segundo, más traicionero, es un escalón. No una subida progresiva. Un día concreto, normalmente entre las 03:00 y las 06:00 UTC, el consumo se dispara y ya no baja. No fue el release. Fue algo externo que descubrió tu dominio.

El tercero es el más caro y el que menos se detecta: Function Duration creciente sin picos de tráfico. Nadie te está atacando. Simplemente tu propio código se ha vuelto lento sin que nadie lo notara.

¿Qué tan rápido se acumula una factura disparada?

Más rápido de lo que la comunidad supone. En hilos de r/nextjs hay casos documentados de $258 en 6 días sobre dominios nuevos sin visitantes reales, y $237 en 6 días sobre planes Pro con proyectos aún sin lanzar. La acumulación es horaria, no mensual: un bot agresivo puede convertir una tarde tranquila en un cargo de tres cifras.

Bots y scrapers: el atacante invisible que dispara Edge Requests

Voy a ser directo: si tienes un sitio público sin firewall configurado explícitamente, no es cuestión de si te va a pasar, sino de cuándo. Los scrapers de LLMs, los agregadores comerciales y los bots SEO no tienen respeto por tu billing. Uno de los casos más citados en la comunidad es una factura de $1.477 sobre un proyecto que ni siquiera estaba lanzado al público.

En un cliente del sector ecommerce, detectamos en junio de 2024 un pico de 14 millones de peticiones en 72 horas. El origen: tres rangos de IPs de un datacenter en Singapur que estaba indexando todo el catálogo cada 4 horas. Sin autenticación. Sin caché.

Cómo detectar tráfico no humano en el dashboard

El panel de Observability te da la pista si sabes dónde mirar. Filtra por User-Agent, ordena por número de peticiones descendente y busca cualquier cosa que no sea un navegador reconocible.

Señales claras: agentes con nombres genéricos tipo python-requests/2.31, Go-http-client, axios/1.4.0. También los que se hacen pasar por Chrome pero piden 40 URLs por segundo desde la misma IP. Ningún humano navega así.

Una comprobación rápida que hacemos: cruzar el país de origen con el mercado real del cliente. Si vendes solo en España y el 60% del tráfico viene de Ohio, no es tu SEO despegando. Es scraping.

Firewall activado no significa firewall configurado

Este es el error que más veces hemos visto. El equipo activa el firewall gestionado, ve el toggle en verde, y asume que está protegido. No lo está.

Por defecto, las reglas gestionadas bloquean amenazas conocidas de nivel bajo. No bloquean scraping legítimo desde datacenters. Para eso hay que crear reglas custom: bloqueo por ASN (los ASNs de AWS, Azure, GCP y OVH suelen ser buenos candidatos si tu público objetivo es humano), rate limiting por IP con umbrales agresivos, y challenges automáticos para User-Agents sospechosos.

¿Funciona siempre? Jamás. Pero reduce el ruido entre un 70% y un 90% en los casos que hemos migrado.

ISR mal aplicado a rutas dinámicas: el error silencioso

Aquí entramos en territorio autoinfligido. Incremental Static Regeneration es una herramienta brillante cuando se usa bien. Y una máquina de quemar dinero cuando se aplica sin criterio a rutas que no deberían cachearse. Hay un caso público muy citado de un equipo que pasó de $70 a $22 al mes solo con revisar la configuración de ISR en rutas dinámicas: reducción del 68% sin tocar el producto.

El patrón que vemos: alguien lee un artículo sobre ISR, decide que "cachear siempre es mejor", y pone revalidate: 60 en rutas del tipo /producto/[id] donde hay 40.000 IDs posibles. Cada visita a un producto sin cachear genera una regeneración. Cada regeneración cuesta una invocación de función.

Cuándo revalidar y cuándo cachear

La regla que aplicamos en nuestro trabajo con equipos que necesitan escalar sin disparar el coste es más pragmática que las guías oficiales: si el número de rutas únicas supera las 5.000 y la tasa de acceso a la long tail es baja, ISR con revalidación agresiva es antieconómico.

¿Alternativas? Cache HTTP tradicional con stale-while-revalidate en CDN, generación bajo demanda con caché de larga duración (24-72 horas) y purga selectiva vía webhook cuando el CMS actualiza. Menos elegante en el discurso. Más barato en la invoice.

Configuración de caché e ISR en un editor de código Next.js

Data Transfer: el consumo que nadie audita hasta que sangra

Nadie mira el Data Transfer. Nunca. Hasta que descubre que representa el 60% del recibo del mes. Y entonces empieza el pánico, que suele desembocar en decisiones peores que el problema original.

El caso que más se repite: una single page application que sirve un bundle de JavaScript de 2,8 MB en cada visita. Con 500.000 visitas mensuales, son 1,4 TB solo de JS. Añade imágenes sin optimizar y respuestas de API sin compresión y llegas a los 4-5 TB fácil.

Imágenes sin optimizar y respuestas API pesadas

Recuerdo un proyecto donde el hero image de la home pesaba 4,2 MB. En PNG. Servido tal cual, sin optimizar, sin variantes. Cada visita cargaba esa barbaridad. La misma imagen, procesada a WebP responsive con tamaños servidos según viewport, quedaba entre 80 KB y 340 KB.

Multiplícalo por tráfico mensual y verás por qué el Image Optimization integrado en la plataforma, bien usado, se paga solo el primer mes.

Con las APIs pasa algo parecido pero peor, porque nadie las mira. Endpoints que devuelven JSONs de 800 KB cuando el cliente solo necesita 12 campos. Falta de compresión gzip o brotli en respuestas. Payloads con objetos anidados sin paginar. Cada byte que sale del servidor cuenta.

Function Duration disparada por consultas N+1 a la base de datos

La cuarta patología es la más difícil de cazar porque no se ve en el tráfico. La ve solo el que sabe leer traces de APM.

Function Duration es lo que cobra la plataforma por cada milisegundo que tu función se ejecuta. Si un endpoint tardaba 120 ms hace tres meses y ahora tarda 1.400 ms, estás pagando casi 12 veces lo mismo por servir la misma petición. Nada ha cambiado en el tráfico. Todo ha cambiado en el importe.

El caso típico del endpoint que se vuelve caro de la noche a la mañana

Mi hipótesis inicial cuando esto pasa suele ser: cambio de infraestructura o cold starts. Casi siempre me equivoco. Después de rastrear cuatro o cinco casos con este patrón exacto, la causa real termina siendo la misma: consultas N+1 que aparecieron con un cambio inocente en un ORM.

Alguien añadió una relación en el modelo. La consulta que antes devolvía 50 registros en una query ahora devuelve 50 más 50×N consultas de lazy loading. En desarrollo, con 10 registros, invisible. En producción, con 12.000 registros, catástrofe.

Herramientas como la extensión pg_stat_statements de PostgreSQL te dicen en 30 segundos cuáles son tus consultas más caras y con qué frecuencia se ejecutan. Si no la tienes activada en tu Postgres, actívala hoy.

Diagnóstico rápido: cómo saber cuál de estas causas es la tuya

Antes de tocar nada, necesitas saber qué está sangrando. Este es el orden que seguimos siempre. No falla.

Paso 1: abre el panel de Usage y ordena las métricas por coste absoluto en el ciclo actual. Anota las tres primeras. Suelen ser: Edge Requests, Function Duration, Data Transfer y, en algunos planes, Build Time o Requests a base de datos.

Paso 2: por cada una de esas tres, compara con el mismo periodo del ciclo anterior. Si alguna se ha multiplicado por más de 3, esa es tu prioridad número uno.

Paso 3: cruza el pico con dos ejes. ¿Coincide con un despliegue tuyo? Entonces es autoinfligido: revisa el diff. ¿No coincide con nada? Entonces es externo: bots, scrapers o un mercado nuevo que te descubrió.

Paso 4, solo si Function Duration es la sospechosa: instrumenta con un APM básico durante 24 horas. Los traces te dirán exactamente qué endpoint y qué consulta se ha vuelto tóxica.

¿Cuándo se pone realmente caro Vercel?

Se pone caro en el momento exacto en que el tráfico automatizado supera al humano y nadie está mirando. La comunidad ha reportado casos de $1.100 sobre cuentas de empleado individual y $1.477 sobre proyectos sin lanzar. El común denominador nunca es el precio de la plataforma: es la ausencia de límites duros y de reglas de firewall custom.

Tratamiento inmediato: límites de gasto, firewall serio y caché

Mientras diagnosticas, el sangrado no espera. Estas son las tres acciones que aplicamos siempre en las primeras 24 horas, en este orden exacto.

Primero: configura Spend Management con un límite duro. No un aviso. Un límite que corte servicio si es necesario. Preferimos un caído de 30 minutos a un recibo de 4.000 dólares. Esta decisión hay que tomarla con el negocio, pero hay que tomarla.

Segundo: reglas de firewall custom. Bloqueo por ASN de datacenters no relevantes para tu mercado. Rate limiting de 60 requests por minuto por IP como línea base. Challenge automático a cualquier User-Agent que contenga bot, crawler, spider excepto los que reconoces (Googlebot, Bingbot).

Tercero: cache-control headers agresivos en rutas estáticas o cuasi-estáticas. public, max-age=86400, stale-while-revalidate=604800 es un buen punto de partida para páginas de contenido. Cada respuesta cacheada en CDN es una invocación de función que no pagas.

Cómo reclamar reembolso a Vercel con argumentos técnicos

Este apartado casi nadie lo cuenta y merece la pena. Cuando el sangrado ha sido causado por tráfico claramente abusivo y no por consumo legítimo, la plataforma tiene margen para hacer ajustes. Los ha hecho en varios casos públicos de la comunidad.

La solicitud funciona mejor si se presenta como un caso técnico documentado, no como una queja emocional. Lo que enviamos siempre: capturas del panel de Observability con los User-Agents ofensivos, tabla con distribución de peticiones por ASN mostrando el peso de datacenters, gráfico del escalón temporal exacto, y confirmación de que el firewall ha sido reforzado tras el incidente.

El correo a soporte debe centrarse en tres puntos: origen no humano identificado, medidas correctivas ya aplicadas, y solicitud concreta de crédito por el consumo anómalo del ciclo. En nuestra experiencia, los tickets bien argumentados obtienen respuesta favorable en un porcentaje razonable de casos. No siempre. Pero merece la tarde que cuesta prepararlo.

Prevención: la configuración que deberías haber tenido desde el día uno

Toda esta lista es lo que nos gustaría haber tenido activo el día que desplegamos el primer proyecto. Nadie la tiene desde el principio. Ahora sí la aplicamos por defecto en cualquier arquitectura que diseñamos.

Alertas de billing en tres umbrales: 50%, 80% y 100% del presupuesto mensual esperado. La del 50% se llega y no pasa nada emocional; la del 80% obliga a mirar; la del 100% activa protocolo de reducción inmediata.

Firewall configurado explícitamente, no gestionado por defecto. Cache-control revisado en cada endpoint en cada PR. APM con traces desde el minuto uno, aunque el proyecto tenga tres usuarios. Es más barato instrumentar antes que investigar después.

Revisiones trimestrales del Usage. Media hora al trimestre. Buscas anomalías, comparas con el trimestre anterior, y actúas sobre cualquier métrica que crezca más rápido que tu tráfico real.

Y una decisión de arquitectura que muchos equipos posponen demasiado: si tu producto ha superado los 500.000 usuarios activos mensuales y sigues 100% en un PaaS serverless, plantéate seriamente una migración de infraestructura cloud parcial hacia contenedores gestionados. No siempre es la respuesta, pero merece cálculo real, no fe.

Diagrama de arquitectura cloud con capas de firewall caché y funciones serverless

Conclusión

Un recibo cloud multiplicado casi nunca tiene una única causa. Suelen ser dos o tres patologías simultáneas que se refuerzan entre sí: un bot que descubrió tu dominio, un ISR mal aplicado que amplifica cada visita, y un endpoint que se volvió lento sin que nadie mirara.

La buena noticia: las cuatro causas se diagnostican en menos de una tarde con las herramientas nativas de la plataforma. La mala: si nadie lo hace, el sangrado sigue mes tras mes hasta que el CFO pregunta. Y ese es siempre el peor momento para descubrirlo.

Founder & Software Engineer
Ingeniero de Software y Desarrollador Full Stack con más de 10 años de experiencia operativa. Dirige MVPtoSaaS aplicando los mismos estándares de fiabilidad y rendimiento exigidos en sistemas industriales y de alta criticidad. El enfoque de Felipe asegura que cada producto que transita por MVPtoSaaS cuente con código limpio, bases de datos optimizadas para alta concurrencia y despliegues automatizados preparados para el crecimiento sostenido sin deuda técnica.