Em algum momento do final de 2022 me convenci de que construir um diretório de hospedagem web seria direto. Agregar dados, gerar páginas, rankear, monetizar. Limpo. Eu já tinha feito plays de SEO programático antes — uma ferramenta de imóveis localizada para um cliente no Reino Unido, um site de comparação de SaaS que atingiu 40k visitas mensais — então achei que HostList seria um projeto de seis semanas. Levou perto de sete meses. E quase quebrou algumas coisas: meu cronograma de sono, a confiança de um dos meus devs juniores, e uma conta do Vercel de £180/mês que eu não tinha orçado.
Este é o post-mortem. O real, não a versão do LinkedIn.
---
O Brief que Escrevi para Mim Mesmo
HostList era suposto ser simples. Um diretório de provedores de hospedagem web — compartilhada, VPS, dedicada, WordPress gerenciado — com páginas individuais para cada provedor, páginas de comparação, páginas de categoria, e páginas baseadas em localização (p.ex. "melhor hospedagem na Alemanha"). Faça as contas: ~400 provedores × vários tipos de página × 20+ combinações de filtro. Você chega a 25.000 páginas mais rápido do que pensaria.
Escolhi Next.js quase sem pensar. Usamos no Seahawk para a maioria dos nossos builds React maiores. O ecossistema é maduro, getStaticProps e getStaticPaths fazem sentido para geração estática pesada em SEO, e pessoalmente acho o roteamento baseado em arquivos mais fácil de raciocinar do que Remix ou Gatsby nessa escala.Next.jsalmost without thinking about it. We use it at Seahawk for most of our bigger React-based builds. The ecosystem is mature,getStaticPropsandgetStaticPathsmake sense for SEO-heavy static generation, and I personally find the file-based routing easier to reason about than Remix or Gatsby at this scale.
A primeira decisão real foi a camada de dados. Descartei um headless CMS bem rápido — não queria pagar os preços do Contentful para 25.000 entradas, e não confiava em um CMS para lidar com escritas programáticas em massa de forma limpa. Acabamos com um banco de dados Postgres no Supabase, com uma leve camada de API Next.js na frente. Essa parte funcionou bem. É praticamente tudo mais que ficou complicado.Supabase, with a lightweight Next.js API layer sitting in front of it. That part actually worked fine. It's almost everything else that got complicated.
---
Geração Estática em Escala: O Que Ninguém Te Avisa
Então, a questão com getStaticPaths e 25.000 rotas. Funciona. Tecnicamente. Mas seus tempos de build vão te fazer questionar as escolhas da sua vida.getStaticPathswith 25,000 routes. It works. Technically. But your build times will make you question your life choices.
Nossa primeira build completa levou 4 horas e 47 minutos. No Vercel. O que, se você não tiver cuidado com seus limites de plano, é o tipo de coisa que causa uma notificação de cobrança às 2 da manhã. Fiquei olhando para aquele alerta do Slack do meu celular e genuinamente considerei só usar WordPress.
A Armadilha do `fallback: 'blocking'`
Meu instinto inicial era pré-renderizar tudo. Cada página, cada combinação. Má ideia — e não pelo motivo que a maioria dos tutoriais te avisa (que geralmente é só "demora um tempo"). O verdadeiro problema é invalidação de cache. Quando um provedor de hosting atualiza seus preços (e atualizam, constantemente), você precisa rebuildar páginas afetadas. Se tudo é pré-renderizado estaticamente sem ISR, você está acionando rebuilds completos para mudanças de dados que afetam talvez 30 páginas de 25.000.cache invalidation. When a hosting provider updates their pricing (and they do, constantly), you need to rebuild affected pages. If everything is statically pre-rendered with no ISR, you're triggering full rebuilds for data changes that affect maybe 30 pages out of 25,000.
Passei para Incremental Static Regeneration com um revalidate de 86400 segundos (24 horas) para a maioria das páginas, e 3600 segundos para páginas de provedores pesadas em preços. Essa foi a melhoria de qualidade de vida única mais importante em todo o projeto. Os tempos de build caíram para menos de 40 minutos porque estávamos pré-renderizando apenas as ~2.000 páginas principais por prioridade de tráfego e deixando o resto gerar sob demanda com fallback: 'blocking'.Incremental Static Regenerationwith arevalidateof 86400 seconds (24 hours) for most pages, and 3600 seconds for pricing-heavy provider pages. This was the single biggest quality-of-life improvement in the entire project. Build times dropped to under 40 minutes because we were only pre-rendering the top ~2,000 pages by traffic priority and letting the rest generate on-demand withfallback: 'blocking'.
Dividindo a Árvore de Rotas
Uma coisa que eu faria diferente, e que falo agora para todo dev na Seahawk que trabalha em um projeto programático grande: divida sua árvore de rotas cedo. Não tenha uma função getStaticPaths monolítica tentando retornar 25.000 slugs. Dividimos a nossa em:getStaticPathsfunction trying to return 25,000 slugs. We broke ours into:
/providers/[slug] — páginas de provedor individual (~400)— individual provider pages (~400)/compare/[slugA]-vs-[slugB] — páginas de comparação lado a lado (~8.000)— head-to-head comparison pages (~8,000)/category/[type] — páginas de landing de categoria (~40)— category landing pages (~40)/location/[country]/[type] — combinações geo × categoria (~16.000+)— geo × category combinations (~16,000+)/best/[use-case] — páginas de listas curadas (~600)— curated list pages (~600)
Cada grupo de rotas tem seu próprio cadência de revalidação, sua própria lógica de busca de dados, e criticamente, sua própria prioridade de build. As páginas de localização são quase inteiramente sob demanda. As páginas de provedor são sempre pré-renderizadas. Separação clara.
---
A Bagunça do Pipeline de Dados (E Como Consertamos)
No início de 2023 cometi o erro de construir o lado de coleta de dados do HostList muito frouxamente. Tínhamos um script de scraping (escrito em Python, usando BeautifulSoup e um pool de proxy rotativo do Webshare), uma Google Sheet manual para correções, e uma tabela Supabase. Três fontes de verdade. Nenhuma delas conversando adequadamente uma com a outra.
Um dev júnior — boa pessoa, saído de um bootcamp — gastou três semanas mantendo um script de sincronização entre a Sheet e Supabase que quebrava toda vez que um nome de coluna mudava. Eu deveria ter eliminado a Sheet na semana um e construído uma UI admin interna apropriada. Eventualmente fizemos, usando Next.js API routes e um dashboard Retool acoplado ao lado, mas queimamos provavelmente 60 horas de engenharia chegando lá.
O conserto: uma única fonte de verdade, sempre. O banco de dados é canônico. Tudo escreve no banco de dados. A UI admin lê e escreve no banco de dados. O scraper escreve no banco de dados. Parece óbvio. Sempre parece, olhando para trás.one source of truth, always. The database is canonical. Everything writes to the database. The admin UI reads from and writes to the database. The scraper writes to the database. Sounds obvious. It always does, in hindsight.
Mantendo Dados Frescos em Escala
Para um diretório deste tamanho, a atualização de dados é uma preocupação de SEO tanto quanto de UX. Google percebe quando tabelas de preço mostram £2.99/mês para um plano que custa £5.99 há oito meses. Configuramos:
- Um job de scrape semanal rodando em um cron Railway (barato, confiável, não requer um servidor dedicado)
- Um webhook de banco de dados Supabase que dispara quando uma coluna price_updated_at muda, acionando um endpoint de revalidação Next.js
price_updated_atcolumn changes, hitting a Next.js revalidation endpoint - Flags de override manual no Retool para os ~30 provedores cujos sites bloqueiam ativamente scrapers
Esse endpoint de revalidação — /api/revalidate?secret=TOKEN&path=/providers/siteground — é um recurso stock Next.js, mas fiá-lo a um webhook de banco de dados exigiu um pouco de trabalho. Valeu cada minuto./api/revalidate?secret=TOKEN&path=/providers/siteground— is a stock Next.js feature, but wiring it to a database webhook took a bit of plumbing. Worth every minute.
---
Arquitetura de SEO: O Que Realmente Mexeu a Agulha
Construí sites de conteúdo o suficiente para saber que ter 25 mil páginas não é a mesma coisa que ter 25 mil páginas que ranqueiam. As páginas de comparação foram a armadilha. Geramos todas as combinações A-vs-B possíveis para nossos ~400 provedores, o que nos deu aproximadamente 79.800 emparelhamentos teóricos. Construímos ~8.000 deles. E a maioria deles era, francamente, fina demais.
Confissão honesta: fiquei ganancioso. A lógica de SEO era sólida — "SiteGround vs Bluehost" gera volume de busca real, a cauda longa de queries de comparação é enorme — mas não construímos conteúdo único o suficiente por página para justificar a existência de cada uma delas. Google começou a rastrear a seção de comparação e claramente decidiu que não valia seu tempo. O próprio guia do Google sobre conteúdo fina é direto sobre isso, e eu deveria ter sido mais direto comigo mesmo antes.Google's own guidance on thin contentis blunt about this, and I should have been blunter with myself earlier.
O Que Fizemos para Recuperar
Eliminamos. Reduzimos as páginas de comparação de ~8.000 para ~1.200 — apenas pares com volume de busca demonstrável (verificado no Ahrefs, mínimo 50 buscas mensais globalmente). Depois enriquecemos as páginas restantes com:
- Seções dinâmicas "para quem é melhor" extraídas de dados estruturados do provedor
- Dados reais de uptime (integramos com uma API de uptime de terceiros)
- Resumos de avaliações de usuários extraídos de dados do Trustpilot quando disponíveis
O resultado foi 1.200 páginas que eram realmente úteis em vez de 8.000 páginas que não eram. O tráfego orgânico para a seção de comparação aumentou 340% nos três meses seguintes. Contraintuitivo até não ser.
Link Interno em Escala
Com 25 mil páginas, o link interno não pode ser manual. Construímos um componente de páginas relacionadas que faz uma query no Supabase em tempo de build (em getStaticProps) e retorna as cinco páginas adjacentes mais relevantes com base na sobreposição de categoria e localização. Nenhuma intervenção editorial necessária. Não é perfeito — ocasionalmente uma página de hospedagem VPS faz link para algo um pouco fora do eixo — mas está 90% correto, e significou que cada página tinha links internos contextualmente relevantes desde o primeiro dia.getStaticProps) and returns the five most relevant adjacent pages based on category and location overlap. No editorial intervention needed. It's not perfect — occasionally a VPS hosting page links to something a bit sideways — but it's 90% right, and it meant every page had contextually relevant internal links from day one.
---
Performance: A Parte Que Te Humilha
Você pensaria que geração estática tornaria o performance fácil. E em um nível conceitual, torna — HTML pré-renderizado, em cache na edge da CDN do Vercel, sem overhead de server-rendering. Mas 25.000 páginas significa 25.000 oportunidades de ter tomado uma decisão ruim sobre sua árvore de componentes.
Nosso maior problema de performance era a tabela de comparação de provedores. Era um componente React pesado do lado do cliente — muito state, muito conditional rendering, usado tanto em páginas de provedores quanto em páginas de comparação. Em mobile, estava causando um Largest Contentful Paint de cerca de 4.8 segundos. Ruim. Realmente ruim para um site onde o tráfego principal são pessoas no meio de uma decisão de compra.Largest Contentful Paintof around 4.8 seconds. Bad. Really bad for a site where the primary traffic is people mid-decision on a purchase.
Reconstruímos como uma tabela estática renderizada no servidor com uma fina camada de hidratação React para as partes interativas do filtro. O LCP caiu para 1.9 segundos. Isso não é mágica — é só fazer a coisa entediante corretamente.
O Problema de Imagens
Todo provedor tem um logo. 400 logos, mais screenshots, previews de UI, ícones de features. Cometemos o erro de hospedar esses na otimização de imagens nativa do Vercel nos primeiros dois meses. Os custos de bandwidth foram silenciosamente horríveis. Movemos tudo para Cloudflare R2 com um domínio customizado, reduzimos nossa conta do Vercel de £180/mês para £40/mês. Se você está construindo qualquer coisa pesada em imagens, olhe para Cloudflare R2 cedo — o egress gratuito é genuinamente útil em escala.Cloudflare R2early — the free egress is genuinely useful at scale.
---
Como o Build Pipeline Realmente Se Parece Agora
Para qualquer um que queira o quadro concreto:
- Coleta de dados — scraper Python em um cron job do Railway, escreve para Postgres do Supabase— Python scraper on a Railway cron job, writes to Supabase Postgres
- Camada admin — dashboard Retool para edições manuais, correções e marcação de provedores— Retool dashboard for manual edits, corrections, and provider flags
- App Next.js — Pages router (começamos antes do App Router ser estável o suficiente para confiar), deployed no Vercel— Pages router (we started before App Router was stable enough to trust), deployed on Vercel
- ISR + revalidação sob demanda — top ~2.000 páginas pré-construídas, resto sob demanda, todas com revalidação a cada 24h— top ~2,000 pages pre-built, rest on-demand, all with 24h revalidation
- Imagens — Cloudflare R2, servidas via subdomínio customizado com CDN do Cloudflare na frente— Cloudflare R2, served via a custom subdomain with Cloudflare CDN in front
- Analytics — Plausible para dados de tráfego com privacidade, Ahrefs para rastreamento de rankings— Plausible for privacy-friendly traffic data, Ahrefs for ranking tracking
- Monitoramento de uptime — BetterUptime observando os cinco tipos de páginas com maior tráfego— BetterUptime watching the five most traffic-heavy page types
Não é glamouroso. Também é em grande parte entediante de manter, que é exatamente o que você quer de uma infraestrutura que vai deixar rodando por três anos.
---
Erros Honestos, Numerados
- Comecei muito amplo. 25.000 páginas sempre foi o objetivo, mas deveria ter lançado com 500 páginas de alta qualidade e expandido. Em vez disso, lancei com tudo e tive um problema de orçamento de crawl do Google pelos primeiros quatro meses.25,000 pages was always the goal, but I should have launched with 500 high-quality pages and expanded. Instead I launched with everything and had a Google crawl budget problem for the first four months.
- Não configurei revalidation corretamente desde o primeiro dia. Desperdicei dois meses com rebuilds completos que ISR teria tornado desnecessários.We wasted two months on full rebuilds that ISR would have made unnecessary.
- Mantive a Google Sheet. Uma única fonte da verdade deveria ter sido inegociável desde a primeira semana.Single source of truth should have been non-negotiable from week one.
- Subestimei a qualidade das páginas de comparação. Volume não é uma estratégia.Volume is not a strategy.
- Usei otimização de imagem do Vercel por muito tempo. Mudei para R2 seis semanas depois do que deveria ter.Moved to R2 six weeks later than we should have.
- Não dividi a árvore de rotas cedo o suficiente. Misturei rotas rápidas e lentas na mesma chamada getStaticPaths e depois me perguntei por que os builds eram lentos.Mixed fast and slow routes in the same
getStaticPathscall and then wondered why builds were slow.
Cada uma dessas é uma decisão que parecia razoável na época. Essa é a parte que tutoriais não capturam — decisões arquiteturais ruins geralmente têm justificativas que soam bem quando você as toma.
---
FAQ
Quanto tempo levou o build inicial para ir ao vivo?
Sete meses do primeiro commit até uma versão que eu me sentia confortável chamando de v1. A primeira versão pública bruta saiu no mês quatro, mas tinha sérios problemas de conteúdo fino e a seção de comparação era praticamente inútil. Eu diria quatro meses para "tecnicamente ao vivo" e mais três para "realmente bom".
Você usaria o App Router se estivesse começando hoje?
Provavelmente sim, para novos projetos iniciados no final de 2023 em diante. Os server components do App Router seriam realmente bem adequados para esse tipo de geração de página com muitos dados. Mas migrar um app Pages Router existente de 25.000 páginas não é um projeto que vou abraçar em breve. O Pages Router ainda funciona, e "funciona" é subestimado.
Como você lida com provedores que saem do negócio ou mudam sua oferta significativamente?
Temos uma flag de status no banco de dados — ativo, descontinuado, redirecionado. Provedores descontinuados ganham uma página de arquivo reduzida em vez de uma remoção completa, o que preserva qualquer backlink. Provedores redirecionados (por exemplo, quando um host adquire outro) ganham um 301 tratado via a config redirects do Next.js em next.config.js. Revisamos as flags de status mensalmente.statusflag in the database —active,deprecated,redirected. Deprecated providers get a slim archive page rather than a full removal, which preserves any backlinks. Redirected providers (e.g. when one host acquires another) get a 301 handled via the Next.jsredirectsconfig innext.config.js. We review the status flags monthly.
O que você usaria em vez do Next.js se estivesse fazendo isso novamente?
Honestamente, não sei. Astro é interessante para sites de conteúdo praticamente estático, e tenho explorado em um projeto menor. Mas Next.js nos deu a flexibilidade de ter seções estáticas e dinâmicas no mesmo codebase, o que importava. Para um diretório puramente estático sem recursos interativos, Astro poderia ser mais rápido de buildar e mais barato de executar. Me pergunte novamente em um ano.
Como você impede que scrapers copiem todo o diretório?
Honestamente? Você não consegue, completamente. Fazemos rate-limit nas rotas de API, usamos o bot management do Cloudflare no frontend, e rotacionamos alguns dos dados estruturados para que cópias feitas por scraping fiquem obsoletas rapidamente. Mas se alguém quer clonar um diretório público, vai encontrar uma forma. O moat é atualização de dados e qualidade de UX, não ofuscação técnica.
---
Pensamento Final
HostList não é um sucesso espetacular. Gera receita — comissões de afiliado, alguns acordos diretos de publicidade — e se classifica razoavelmente bem para talvez 600 dos termos que originalmente direcionei. Tudo bem. Foi um projeto de aprendizado que também gera receita, o que é o melhor tipo.
Se você está pensando em construir um site de SEO programático em larga escala em Next.js, meu conselho honesto é este: faça. É genuinamente uma boa stack para o trabalho. Mas construa menos do que você pensa que precisa, construa melhor do que você pensa que tem tempo para fazer, e organize sua arquitetura de dados antes de escrever um único template de página.
A tecnologia é a parte fácil. Sempre é.
