大约在 2022 年底,我说服自己构建一个虚拟主机目录会很直接。汇总数据、生成页面、排名、变现。简洁。我之前做过程序化 SEO,为一家英国客户做过本地化房地产工具,做过一个达到月访问量 40k 的 SaaS 对比网站,所以我以为 HostList 会是一个六周的项目。结果花了接近七个月。它几乎打破了一些东西:我的睡眠时间表、我一个初级开发者的信心,还有我没有预算的 180 英镑/月的 Vercel 账单。
这是事后分析。真实的那个,不是 LinkedIn 版本的。
---
我为自己写的简介
HostList 本应很简单。一个虚拟主机提供商的目录,包括共享主机、VPS、独立主机、托管 WordPress,每个提供商有单独的页面、对比页面、分类页面和基于地点的页面(比如"德国最佳主机")。算一下数学:约 400 个提供商 × 若干页面类型 × 20+ 个筛选组合。你会比想象中更快地达到 25000 页面。
我选择 Next.js 几乎没有经过多想。我们在 Seahawk 用它来做大多数较大的基于 React 的构建。生态系统成熟,getStaticProps 和 getStaticPaths 对于 SEO 密集的静态生成很有意义,我个人觉得基于文件的路由比 Remix 或 Gatsby 在这个规模下更容易推理。
第一个真正的决策是数据层。我很快排除了无头 CMS,我不想为 25000 个条目支付 Contentful 的价格,而且我不相信 CMS 能够干净地处理批量程序化写入。我们选择了 Supabase 上的 Postgres 数据库,前面有一个轻量级的 Next.js API 层。那部分实际上工作得很好。几乎其他的一切都变得复杂了。
---
大规模静态生成:没人会告诉你的事
这就是关于 getStaticPaths 处理 25,000 条路由的问题所在。它能工作。从技术上讲。但你的构建时间会让你质疑人生选择。
我们第一次完整构建花了 4 小时 47 分钟。在 Vercel 上。如果你不小心管理你的计划限制,这种情况会导致凌晨 2 点收到一条账单提醒。我从手机上盯着那条 Slack 通知,真的想过直接用 WordPress 算了。
`fallback: 'blocking'` 陷阱
我最初的本能是预渲染所有内容。每个页面、每个组合。这个主意不好,理由不是大多数教程警告你的那样(通常只是"它需要一段时间")。真正的问题是缓存失效。当虚拟主机提供商更新他们的定价(他们确实在不断地这样做)时,你需要重建受影响的页面。如果所有内容都是通过静态预渲染且没有 ISR 的,你会为影响可能 25000 页中 30 页的数据变化触发完整重建。
我切换到了增量静态再生,对大多数页面的 revalidate 设置为 86400 秒(24 小时),对定价密集的提供商页面设置为 3600 秒。这是整个项目中最大的生活质量改善。构建时间降至 40 分钟以下,因为我们只按流量优先级预渲染大约 2,000 个顶级页面,让其余的按需生成,并使用 fallback: 'blocking'。
分割路由树
有一件事我会做得不同,我现在对每个接触大型编程项目的 Seahawk 开发者都会说:尽早分割你的路由树。不要有一个单一的庞大的 getStaticPaths 函数试图返回 25,000 个 slug。我们把它分成了:
- /providers/[slug],个人提供商页面(约 400 页)
- /compare/[slugA]-vs-[slugB],一对一对比页面(约 8000 页)
- /category/[type],分类落地页(约 40 页)
- /location/[country]/[type],地理 × 分类组合(16000+ 页)
- /best/[use-case],精选列表页面(约 600 页)
每个路由组都有自己的重新验证周期、自己的数据获取逻辑,最关键的是,有自己的构建优先级。位置页面几乎完全按需生成。提供商页面总是预先渲染的。干净的分离。
---
数据管道的混乱(以及我们如何解决的)
早在 2023 年初,我犯了一个错误,在 HostList 数据收集端构建得太松散了。我们有一个爬虫脚本(用 Python 编写,使用 BeautifulSoup 和来自 Webshare 的轮转代理池)、一个手动 Google Sheet 用于更正,还有一个 Supabase 表。三个真实来源。它们之间没有正常沟通。
一个初级开发者,好孩子,刚从训练营毕业,花了三周维护一个 Sheet 和 Supabase 之间的同步脚本,每次改列名都会崩溃。我应该在第一周就干掉 Sheet,建一个正经的内部管理后台。最后我们还是做了,用 Next.js API 路由和一个 Retool 仪表板拼凑在一起,但我们浪费了大概 60 个工程小时才搞定。
解决方案:一个信息源,始终如一。数据库是规范来源。所有数据都写入数据库。管理UI从数据库读取和写入。爬虫写入数据库。听起来很显而易见。事后看来,确实总是这样。
大规模保持数据新鲜度
对于这个规模的目录,数据新鲜度既是 SEO 问题,也是 UX 问题。Google 会注意到定价表显示某个计划是 £2.99/月,但它已经是 £5.99 有八个月了。我们设置了:
- 在 Railway cron 上运行的每周爬取任务(便宜、可靠、不需要专用服务器)
- 一个 Supabase 数据库 webhook,当 price_updated_at 列改变时触发,命中一个 Next.js 重新验证端点
- 在 Retool 中为大约 30 个主动阻止爬虫的提供商设置手动覆盖标志
那个重新验证端点 /api/revalidate?secret=TOKEN&path=/providers/siteground 是 Next.js 的标准功能,但把它连到数据库 webhook 需要一些管道工作。完全值得。
---
SEO 架构:真正起作用的是什么
我建设过足够多的内容网站,知道拥有25,000个页面和拥有25,000个排名的页面不是一回事。对比页面成了陷阱。我们为大约400个供应商生成了所有可能的A-vs-B组合,理论上产生了大约79,800个配对。我们实际建设了约8,000个。老实说,它们大多都很薄弱。
老实说:我贪心了。SEO 逻辑是对的,"SiteGround vs Bluehost" 确实有搜索量,对比查询的长尾很可观,但我们没有为每一个页面积累足够的独特内容来证明它的存在价值。Google 开始爬取对比版块,显然觉得不值得花时间。Google 自己的瘦内容指南言辞直白,我应该更早对自己严厉一点。
我们采取的恢复措施
我们删掉了。把对比页面从大概 8,000 页砍到 1,200 页,只保留有可验证搜索量的配对(用 Ahrefs 验证过,最少 50 次月搜索量全球)。然后我们用以下方式丰富了剩下的页面:
- 从结构化供应商数据提取的动态"最适合谁"部分
- 真实正常运行时间数据(我们集成了第三方正常运行时间API)
- 用户评论摘要,尽可能从Trustpilot数据中获取
结果是1,200个实际有用的页面,而不是8,000个没用的页面。对比部分的有机流量在接下来的三个月内增长了340%。看似反直觉,直到它不再是。
这个规模下的内部链接
有 25,000 个页面,内部链接不可能手工做。我们构建了一个相关页面组件,在构建时查询 Supabase(在 getStaticProps 中),根据分类和地理位置重叠返回五个最相关的邻近页面。不需要编辑干预。不是完美的,偶尔 VPS 主机页面会链到有点歪的东西,但准确度 90% 左右,意思是每个页面从第一天就有上下文相关的内部链接。
---
性能:谦虚你的部分
你会以为静态生成会让性能变简单。概念上确实是这样,预渲染的 HTML,在 Vercel CDN 边缘缓存,没有服务器渲染开销。但 25,000 页意味着 25,000 个你可能在组件树上做错决定的机会。
我们最大的性能问题是供应商对比表。这是一个重量级的客户端 React 组件,大量状态,大量条件渲染,用在供应商页面和对比页面上。在手机上,它导致最大内容绘制大概 4.8 秒。糟糕。对于一个主要流量是购买决策中间的人的网站来说,真的很糟。
我们把它重构成了一个服务器渲染的静态表格,配一个薄 React 水合层用于交互过滤部分。LCP 降到了 1.9 秒。这不是魔法,就是把无聊的事情做对。
图片问题
每个供应商都有个logo。400 个 logo,加上截图、UI 预览、功能图标。我们在前两个月犯了个错误,用 Vercel 内置的图片优化来托管它们。带宽成本悄悄地可怕。全部移到 Cloudflare R2 带自定义域名,Vercel 账单从 180 英镑/月降到 40 英镑/月。如果你在做任何图片密集的东西,早点看看 Cloudflare R2,免费的出站流量在规模上真的有用。
---
构建管道现在实际上是什么样子的
对于想要具体情况的人:
- 数据收集,Python scraper 在 Railway cron 任务上,写到 Supabase Postgres
- 管理层、Retool 仪表板用于手动编辑、更正和提供商标记
- Next.js 应用、Pages 路由(我们在 App Router 足够稳定之前就开始了),部署在 Vercel 上
- ISR + 按需重新验证,约 2,000 个顶级页面预构建,其余按需构建,全部使用 24 小时重新验证
- 图像、Cloudflare R2,通过自定义子域提供,前面配备 Cloudflare CDN
- 分析、Plausible 用于隐私友好的流量数据、Ahrefs 用于排名追踪
- 正常运行时间监控、BetterUptime 监控五个流量最大的页面类型
这不是什么光鲜亮丽的事情。维护起来也大多很枯燥,这正是你想要的——基础设施要能就这样运行三年而不出问题。
---
诚实的错误,按顺序排列
- 起点太宽泛。25,000 页面始终是目标,但我应该以 500 个高质量页面启动并逐步扩展。结果我以全部内容启动,前四个月出现了谷歌爬虫预算问题。
- 没有从第一天就正确设置重新验证。我们浪费了两个月进行完整重建,ISR 本来可以避免这些。
- 保留了谷歌表格。单一信息源应该从第一周就成为不可谈判的需求。
- 低估了比较页面的质量。数量不是策略。
- Vercel 图片优化用得太久了。应该提前六周迁移到 R2,但没有。
- 没有尽早拆分路由树。在同一个getStaticPaths调用中混合了快速和慢速路由,然后对构建为什么很慢感到困惑。
这些都是当时看起来合理的决定。这是教程没有涵盖的部分,糟糕的架构决定通常在你做出决定时都有听起来不错的理由。
---
常见问题
初始构建上线用了多长时间?
从第一次提交到我满意地称之为 v1 的版本,花了七个月。首个粗糙的公开版本在第四个月左右上线,但存在严重的内容不足问题,对比部分基本没用。我会说四个月是"技术上的上线",再加三个月才是"真正的成品"。
如果你现在开始新项目,会使用 App Router 吗?
可能会,对于 2023 年末之后开始的新项目来说。App Router 的服务器组件实际上很适合这种数据密集型的页面生成。但把一个现有的 25,000 页面的 Pages Router 应用迁移过来,这不是我近期要做的项目。Pages Router 仍然可用,而"可用"这点往往被低估了。
如果提供商倒闭或大幅改变他们的服务,你怎么处理?
我们在数据库中有一个状态标志,active、deprecated、redirected。已弃用的提供商获得精简存档页面而不是完全删除,这保留了任何反向链接。已重定向的提供商(例如当一个主机收购另一个时)通过 next.config.js 中的 Next.js redirects 配置获得 301 处理。我们每月审查一次状态标志。
如果重新做一遍,你会用什么代替 Next.js?
我真的不知道。Astro 对于大多是静态内容的网站很有意思,我也在一个较小的项目上玩过。但 Next.js 让我们有灵活性在同一个代码库中既有静态又有动态部分,这很重要。对于一个完全静态的目录且没有交互功能,Astro 可能会更快地构建,运行成本也更低。一年后再问我吧。
你怎样阻止爬虫复制整个目录?
老实说?你做不到,完全做不到。我们对 API 路由进行速率限制,在前端使用 Cloudflare 的机器人管理,并轮换一些结构化数据,这样被爬取的副本会很快过时。但如果有人想克隆一个公开的目录,他们总会找到办法。竞争优势在于数据的新鲜度和用户体验质量,而不是技术混淆。
---
结语
HostList 不是一个爆炸性的成功。它赚钱,联盟佣金、一些直接广告交易,并且在我最初目标的大约 600 个术语中排名相当不错。这很好。这是一个学习项目,同时也恰好能产生收益,这是最好的那种。
如果你正在考虑用 Next.js 构建大规模的程序化 SEO 站点,我的诚实建议是:去做。这真的是这份工作的一个很好的技术栈。但是要构建比你认为需要的更少的内容,要把它做得比你认为有时间做的更好,而且要在写任何页面模板之前先把数据架构整理好。
技术部分是容易的。总是这样。
