使用构建时数据和即时搜索重建我的静态博客

发布: (2026年2月5日 GMT+8 01:51)
6 min read
原文: Dev.to

Source: Dev.to

Cover image for Rebuilding My Static Blog with Build-Time Data and Instant Search

静态站点本应快速、简洁且可靠。随着时间的推移,我的个人博客开始表现得像一个动态应用——运行时的 API 调用、到处都是分页逻辑,以及分散在各个平台的浏览量统计。

上周,我重新构建了 ravgeet.in(Nuxt.js)的博客部分,以彻底解决这些问题。最终的产出仍然是一个静态站点,但现在它感觉 活力十足:聚合的浏览量、即时搜索与排序,并且在运行时不依赖任何外部 API。

本文将详细介绍这次重构背后的思考、架构以及权衡取舍。

我的旧设置的问题

最初,我的博客是这样运作的:

  • 博客内容存放在 Hashnode(权威来源)
  • 部分文章也会同步发布到 Dev.to
  • 页面在 运行时 使用 Hashnode 的 GraphQL API 获取博客数据
  • 分页逻辑(hasNextPage、光标)写在 UI 中

缺点

  • 依赖实时 API 的静态站点感觉不妥
  • 本地开发和构建速度慢且不稳定
  • 添加搜索或排序等功能会需要更多的 API

我希望博客保持静态——但更智能。

构建时数据作为契约

将所有外部数据获取移至构建时,并将结果视为不可变的静态数据。

而不是在运行时获取博客,我引入了一个构建步骤,该步骤:

  • 从 Hashnode 获取博客
  • 从 Dev.to 获取文章
  • 跨平台匹配相同的文章
  • 汇总浏览次数
  • 将所有内容写入单个 JSON 文件

在运行时,站点仅从该 JSON 读取。

Hashnode + Dev.to

Build‑time fetch & normalize

static/blogs.json

Nuxt UI (search, sort, views)

Source:

获取并聚合博客数据

Hashnode:规范内容

Hashnode 仍然是以下信息的唯一来源:

  • 标题、slug、内容、标签
  • 发布日期
  • 封面图片
  • 基础浏览次数

所有文章均通过 Hashnode 的 GraphQL API 获取,分页逻辑在 Node.js 脚本中处理。

Dev.to:分发与额外曝光

Dev.to 能带来额外的读者,因此忽略这些浏览量显得不妥。使用 Dev.to API(配合个人访问令牌),我会抓取我的所有文章并提取:

  • slug
  • canonical_url
  • page_views_count

跨平台文章匹配

文章匹配采用分层策略:

  1. Slug 匹配
  2. Canonical URL 匹配
  3. 标题匹配

匹配成功后,最终的浏览次数计算为:

combinedViews = hashnodeViews + devtoViews;

每篇博客的输出包括:

  • 合并后的浏览次数
  • 各平台的浏览次数(用于调试)
  • Dev.to 链接(若匹配成功)

编写静态数据合约

所有处理后的数据写入 static/blogs.json。该文件:

  • 在构建时生成
  • 被 Git 忽略
  • 在应用中视为只读

文件中还会包含上次更新时间和博客总数等元数据,实质上取代了我原有的整个博客 API。

替换运行时 API 为静态服务

之前,services/blogs.js 会进行实时的 GraphQL 调用。重构后:

  • 服务动态导入 blogs.json
  • findfindOnesearch 在本地运行
  • 没有 Axios,没有分页状态,也没有网络故障

从 UI 的角度来看,未发生任何变化——但在内部,一切都变得可预测。

即时搜索与排序

所有博客数据均在本地,搜索变得非常简单。我添加了:

  • 客户端文本搜索(标题、摘要、标签)

排序选项

  • 发布日期(最近 / 最旧)
  • 浏览次数(最多 / 最少)

因为数据集小且是静态的:

  • 搜索结果即时返回
  • 无需防抖处理
  • 无加载状态
  • 排序是确定性的

这大幅提升了可发现性,同时无需引入搜索服务。

权衡与经验教训

这种方法并不完美:

  • 构建时间略有增加
  • JSON 文件会随时间增长
  • 不适用于实时分析

但对于个人博客来说,这些权衡是值得的。关键要点

  • 静态并不等于无生气
  • 构建时数据管道被低估了
  • 一个简洁的数据契约可以简化 UI、UX 和性能

如果你感兴趣,完整实现位于 ravgeet.in repository

Back to Blog

相关文章

阅读更多 »