A finales de 2022 me convencí de que construir un directorio de alojamiento web sería directo. Agregar datos, generar páginas, posicionar, monetizar. Limpio. Había hecho jugadas de SEO programático antes, una herramienta de bienes raíces localizada para un cliente en UK, un sitio de comparación de SaaS que llegó a 40k visitas mensuales, así que pensé que HostList sería un proyecto de seis semanas. Tardó cerca de siete meses. Y casi rompe varias cosas: mi horario de sueño, la confianza de uno de mis desarrolladores junior, y una factura de Vercel de £180/mes que no había presupuestado.
Este es el post-mortem. El real, no la versión de LinkedIn.
---
El brief que me escribí a mí mismo
HostList se suponía que sería simple. Un directorio de proveedores de alojamiento web, compartido, VPS, dedicado, WordPress administrado, con páginas individuales para cada proveedor, páginas de comparación, páginas de categoría, y páginas basadas en ubicación (p.ej. "mejor hosting en Alemania"). Haz las matemáticas: ~400 proveedores × varios tipos de página × 20+ combinaciones de filtros. Llegas a 25,000 páginas más rápido de lo que crees.
Elegí Next.js casi sin pensarlo. En Seahawk lo usamos para la mayoría de nuestros proyectos más grandes basados en React. El ecosistema está maduro, getStaticProps y getStaticPaths tienen sentido para la generación estática pesada en SEO, y personalmente encuentro el enrutamiento basado en archivos más fácil de razonar que Remix o Gatsby a esta escala.
La primera decisión real fue la capa de datos. Descarté un CMS headless bastante rápido, no quería pagar tarifas de Contentful por 25,000 entradas, y no confiaba en que un CMS manejara escrituras programáticas en masa de forma limpia. Terminamos con una base de datos Postgres en Supabase, con una capa ligera de API de Next.js frente a ella. Esa parte funcionó bien en realidad. Es casi todo lo demás lo que se complicó.
---
Generación Estática a Escala: Lo Que Nadie Te Advierte
Esto es lo que pasa con getStaticPaths y 25,000 rutas. Funciona. Técnicamente. Pero tus tiempos de compilación te harán cuestionar tus decisiones en la vida.
Nuestra primera compilación completa tomó 4 horas y 47 minutos. En Vercel. Lo que, si no tienes cuidado con los límites de tu plan, es el tipo de cosa que causa una notificación de facturación a las 2am. Miré esa alerta de Slack desde mi teléfono y genuinamente consideré simplemente usar WordPress.
La Trampa de `fallback: 'blocking'`
Mi instinto inicial era pre-renderizar todo. Cada página, cada combinación. Mala idea, y no por la razón que la mayoría de tutoriales te advierten (que suele ser solo "toma un tiempo"). El problema real es la invalidación de caché. Cuando un proveedor de hosting actualiza sus precios (y lo hace, constantemente), necesitas reconstruir las páginas afectadas. Si todo está pre-renderizado estáticamente sin ISR, estás disparando reconstrucciones completas para cambios de datos que afectan tal vez 30 páginas de 25,000.
Cambié a Incremental Static Regeneration con un revalidate de 86400 segundos (24 horas) para la mayoría de páginas, y 3600 segundos para páginas de proveedores con mucho contenido de precios. Este fue el mejoramiento más significativo de calidad de vida en todo el proyecto. Los tiempos de compilación cayeron a menos de 40 minutos porque solo estábamos pre-renderizando las ~2,000 páginas principales por prioridad de tráfico y dejando que el resto se generara bajo demanda con fallback: 'blocking'.
Dividiendo el Árbol de Rutas
Una cosa que haría diferente, y que ahora le digo a cada desarrollador en Seahawk que toque un proyecto programático grande: divide tu árbol de rutas temprano. No tengas una función monolítica getStaticPaths tratando de devolver 25,000 slugs. Dividimos la nuestra en:
- /providers/[slug], páginas de proveedores individuales (~400)
- /compare/[slugA]-vs-[slugB], páginas de comparación cara a cara (~8,000)
- /category/[type], páginas de destino de categoría (~40)
- /location/[country]/[type], combinaciones de geo × categoría (~16,000+)
- /best/[use-case], páginas de listas curadas (~600)
Cada grupo de rutas tiene su propio cadence de revalidación, su propia lógica de obtención de datos, y críticamente, su propia prioridad de compilación. Las páginas de ubicación son casi completamente bajo demanda. Las páginas de proveedores siempre se pre-renderizan. Separación clara.
---
El Desastre de la Canalización de Datos (Y Cómo Lo Arreglamos)
A principios de 2023 cometí el error de construir el lado de recopilación de datos de HostList demasiado suelto. Teníamos un script de scraping (escrito en Python, usando BeautifulSoup y un pool de proxies rotativo de Webshare), una Google Sheet manual para correcciones, y una tabla de Supabase. Tres fuentes de verdad. Ninguna de ellas comunicándose adecuadamente entre sí.
Un dev junior, buena persona, recién salido de un bootcamp, pasó tres semanas manteniendo un script de sincronización entre la Sheet y Supabase que se rompía cada vez que cambiaba el nombre de una columna. Debería haber eliminado la Sheet en la primera semana y haber construido una interfaz de admin interna decente. Al final lo hicimos, usando rutas de API de Next.js y un dashboard de Retool pegado al costado, pero quemamos probablemente 60 horas de ingeniería para llegar ahí.
La solución: una única fuente de verdad, siempre. La base de datos es canónica. Todo escribe en la base de datos. La interfaz de administración lee y escribe en la base de datos. El scraper escribe en la base de datos. Suena obvio. Siempre lo hace, en retrospectiva.
Mantener Datos Frescos a Escala
Para un directorio de este tamaño, la frescura de datos es una preocupación de SEO tanto como de UX. Google se da cuenta cuando las tablas de precios muestran £2.99/mes para un plan que ha estado en £5.99 durante ocho meses. Configuramos:
- Un trabajo de scrape semanal ejecutándose en un cron de Railway (barato, confiable, no requiere un servidor dedicado)
- Un webhook de base de datos Supabase que se activa cuando cambia una columna price_updated_at, golpeando un endpoint de revalidación Next.js
- Banderas de anulación manual en Retool para los ~30 proveedores cuyos sitios bloquean activamente scrapers
Ese endpoint de revalidación, /api/revalidate?secret=TOKEN&path=/providers/siteground, es una característica estándar de Next.js, pero conectarlo a un webhook de base de datos requirió algo de trabajo. Valió cada minuto.
---
Arquitectura de SEO: Lo Que Realmente Movió la Aguja
He construido suficientes sitios de contenido para saber que tener 25,000 páginas no es lo mismo que tener 25,000 páginas que rankeen. Las páginas de comparación fueron la trampa. Generamos cada combinación A-vs-B posible para nuestros ~400 proveedores, lo que nos dio aproximadamente 79,800 emparejamientos teóricos. Construimos ~8,000 de ellos. Y la mayoría de ellos eran, francamente, delgados.
Confesión honesta: me dejé llevar por la avaricia. La lógica de SEO era sólida, "SiteGround vs Bluehost" genera volumen de búsqueda real, la cola larga de consultas de comparación es enorme, pero no construimos suficiente contenido único por página para justificar la existencia de cada una. Google empezó a rastrear la sección de comparaciones y claramente decidió que no valía su tiempo. La propia guía de Google sobre contenido delgado es blunt al respecto, y debería haber sido más blunt conmigo mismo antes.
Qué Hicimos para Recuperarnos
Hicimos una criba. Redujimos las páginas de comparación de ~8,000 a ~1,200, solo pares con volumen de búsqueda demostrable (verificado en Ahrefs, mínimo 50 búsquedas mensuales globales). Luego enriquecimos las páginas restantes con:
- Secciones dinámicas "para quién es mejor" extraídas de datos estructurados del proveedor
- Datos de uptime reales (nos integramos con una API de uptime de terceros)
- Resúmenes de reseñas de usuarios sembrados desde datos de Trustpilot cuando están disponibles
El resultado fue 1,200 páginas que realmente eran útiles en lugar de 8,000 páginas que no lo eran. El tráfico orgánico a la sección de comparación aumentó 340% durante los siguientes tres meses. Contraintuitivo hasta que no lo es.
Enlazado Interno a Esta Escala
Con 25,000 páginas, los enlaces internos no pueden ser manuales. Construimos un componente de páginas relacionadas que consulta Supabase en tiempo de construcción (en getStaticProps) y devuelve las cinco páginas adyacentes más relevantes basadas en superposición de categoría y ubicación. Sin intervención editorial necesaria. No es perfecto, ocasionalmente una página de hosting VPS enlaza con algo un poco tangencial, pero es 90% correcto, y significó que cada página tuviera enlaces internos contextuales relevantes desde el primer día.
---
Rendimiento: La parte que te humilla
Uno pensaría que la generación estática haría el desempeño fácil. Y a nivel conceptual, lo hace, HTML pre-renderizado, almacenado en caché en la CDN de Vercel, sin sobrecarga de renderización del servidor. Pero 25,000 páginas significa 25,000 oportunidades de haber tomado una mala decisión sobre tu árbol de componentes.
Nuestro mayor problema de desempeño era la tabla de comparación de proveedores. Era un componente React pesado del lado del cliente, mucho estado, mucho renderizado condicional, usado tanto en páginas de proveedor como en páginas de comparación. En móvil, estaba causando un Largest Contentful Paint de alrededor de 4.8 segundos. Malo. Realmente malo para un sitio donde el tráfico principal son personas en medio de una decisión de compra.
Lo reconstruimos como una tabla estática renderizada por servidor con una capa de hidratación React delgada para los bits interactivos del filtro. El LCP bajó a 1.9 segundos. Eso no es magia, es solo hacer lo aburrido correctamente.
El problema de las imágenes
Cada proveedor tiene un logo. 400 logos, más capturas de pantalla, vistas previas de UI, iconos de características. Cometimos el error de alojarlos en la optimización de imágenes integrada de Vercel los primeros dos meses. Los costos de ancho de banda fueron silenciosamente horribles. Movimos todo a Cloudflare R2 con un dominio personalizado, redujimos nuestra factura de Vercel de £180/mes a £40/mes. Si estás construyendo algo pesado en imágenes, mira Cloudflare R2 temprano, el egreso gratuito es genuinamente útil a escala.
---
Cómo se ve realmente el pipeline de construcción ahora
Para cualquiera que quiera el panorama concreto:
- Recopilación de datos, scraper de Python en un cron job de Railway, escribe en Supabase Postgres
- Capa administrativa, panel de control Retool para ediciones manuales, correcciones y marcadores de proveedores
- App Next.js, Pages router (comenzamos antes de que App Router fuera lo suficientemente estable para confiar), desplegado en Vercel
- ISR + revalidación bajo demanda, ~2,000 páginas principales pre-construidas, el resto bajo demanda, todas con revalidación cada 24h
- Imágenes, Cloudflare R2, servidas a través de un subdominio personalizado con CDN de Cloudflare al frente
- Análisis, Plausible para datos de tráfico respetuosos con la privacidad, Ahrefs para seguimiento de rankings
- Monitoreo de disponibilidad, BetterUptime vigilando los cinco tipos de páginas con más tráfico
No es glamoroso. Tampoco es particularmente emocionante mantenerlo, que es exactamente lo que quieres de una infraestructura que vas a dejar corriendo durante tres años.
---
Errores Honestos, Numerados
- Empecé demasiado amplio. 25,000 páginas siempre fue el objetivo, pero debería haber lanzado con 500 páginas de alta calidad y expandido. En cambio, lancé con todo y tuve un problema de presupuesto de rastreo de Google durante los primeros cuatro meses.
- No configuré la revalidación correctamente desde el primer día. Desperdiciamos dos meses en reconstrucciones completas que ISR habría hecho innecesarias.
- Mantuve la Hoja de Cálculo de Google. La única fuente de verdad debería haber sido no negociable desde la primera semana.
- Subestimé la calidad de las páginas de comparación. El volumen no es una estrategia.
- Usamos optimización de imágenes de Vercel demasiado tiempo. Nos pasamos a R2 seis semanas más tarde de lo que debería haber sido.
- No dividimos el árbol de rutas lo suficientemente temprano. Mezclamos rutas rápidas y lentas en la misma llamada a getStaticPaths y luego nos preguntábamos por qué las compilaciones eran lentas.
Cada una de estas es una decisión que parecía razonable en su momento. Esa es la parte que los tutoriales no capturan: las malas decisiones arquitectónicas generalmente tienen justificaciones que suenan bien cuando las tomas.
---
FAQ
¿Cuánto tiempo tardó la compilación inicial en ponerse en vivo?
Siete meses desde el primer commit hasta una versión con la que me sentía cómodo llamando v1. La primera versión pública áspera estuvo en línea alrededor del mes cuatro, pero tenía problemas serios de contenido delgado y la sección de comparación era prácticamente inútil. Diría que cuatro meses para "técnicamente en línea" y otros tres para "realmente bueno".
¿Usarías App Router si empezaras hoy?
Probablemente sí, para nuevos proyectos iniciados a finales de 2023 en adelante. Los componentes de servidor de App Router serían realmente adecuados para este tipo de generación de páginas con mucho contenido de datos. Pero migrar una aplicación existente de Pages Router de 25,000 páginas no es un proyecto que vaya a emprender en el corto plazo. Pages Router aún funciona, y que "funcione" es algo subestimado.
¿Cómo manejas proveedores que cierran o cambian significativamente su oferta?
Tenemos un indicador de estado en la base de datos: active, deprecated, redirected. Los proveedores deprecated reciben una página de archivo reducida en lugar de una eliminación completa, lo que preserva cualquier enlace entrante. Los proveedores redirected (por ejemplo, cuando un host adquiere a otro) obtienen un 301 manejado a través de la configuración de redirects de Next.js en next.config.js. Revisamos los indicadores de estado mensualmente.
¿Qué usarías en lugar de Next.js si lo hicieras de nuevo?
Honestamente no sé. Astro es interesante para sitios de contenido mayormente estático, y he estado jugando con él en un proyecto más pequeño. Pero Next.js nos dio la flexibilidad de tener secciones tanto estáticas como dinámicas en la misma base de código, lo que importaba. Para un directorio puramente estático sin características interactivas, Astro podría ser más rápido de compilar y más barato de ejecutar. Pregúntame de nuevo en un año.
¿Cómo evitas que los scrapers copien todo el directorio?
¿Honestamente? No puedes, completamente. Limitamos la velocidad de las rutas de API, usamos la gestión de bots de Cloudflare en el frontend, y rotamos algunos de los datos estructurados para que las copias scrapeadas se vuelvan obsoletas rápidamente. Pero si alguien quiere clonar un directorio de acceso público, va a encontrar la manera. El diferencial competitivo es la actualización de datos y la calidad de UX, no la ofuscación técnica.
---
Pensamiento Final
HostList no es un éxito arrollador. Genera dinero, comisiones de afiliados, algunos acuerdos directos de publicidad, y ocupa posiciones razonables para quizás 600 de los términos que originalmente me propuse. Está bien. Fue un proyecto de aprendizaje que además genera ingresos, que es lo mejor que puede pasar.
Si estás pensando en construir un sitio SEO programático a gran escala en Next.js, mi consejo honesto es este: hazlo. Es genuinamente un stack excelente para el trabajo. Pero construye menos de lo que crees necesitar, construyelo mejor de lo que piensas que tienes tiempo, y resuelve tu arquitectura de datos antes de escribir un solo template de página.
La tecnología es la parte fácil. Siempre lo es.
