探秘博客构建的秘诀:ISR 增量静态再生与自动化内容更新
你也不想你的博客在更新之后需要重新构建整个网站吧,那也太费时费力了。不妨尝试一下 ISR 增量静态再生,让你的博客获得闪电般的更新体验。记录我在 Next.js 博客中使用 ISR、Webhook 与 GitHub Actions 实现自动化内容更新的实践,以及一次关于 Vercel 架构的踩坑经历
本文目录20 个章节
在构建一个现代博客系统时,性能与开发体验往往是一对矛盾。
传统动态网站:
用户访问
↓
服务器查询数据库
↓
生成页面
↓
返回 HTML虽然数据实时,但是每一次访问都需要服务器计算。
而纯静态网站:
next build
↓
生成 HTML
↓
部署 CDN拥有极快的访问速度,但是每次内容更新都需要重新构建整个项目。
对于博客、文档站这类内容驱动型网站来说,我们希望同时拥有:
- 静态页面的极致性能;
- 动态内容的快速更新;
- 无需手动重新部署。
这也是我最终选择:
Next.js ISR + Webhook + GitHub Actions
的原因。
一次失败的 Webhook 架构
最开始,我设计了一套看起来很合理的流程:
Pages CMS
↓
GitHub Commit
↓
GitHub Webhook
↓
Vercel API Route
↓
syncPosts()
↓
读取 content/posts
↓
更新数据库
↓
revalidateTag()理论上:
CMS 修改文章后:
- 提交 Markdown 文件;
- GitHub 通知 Vercel;
- Vercel 同步最新文章;
- 刷新 ISR 缓存。
但是实际运行后发现:
无论怎么修改文章,Webhook 触发之后始终返回:
no content changed刚开始我以为是同步逻辑的问题。
但是进一步排查后发现:
真正的问题在于:
Vercel Runtime 并不是 Git 仓库。
Vercel Deployment 的核心机制
很多人第一次使用 Serverless 平台时容易产生一个误区:
GitHub 更新了代码,Vercel Function 里面的文件也会自动更新。
实际上并不是这样。
Vercel 的部署模型:
GitHub Repository
↓
Build
↓
Immutable Deployment
↓
Serverless Function一次部署生成的是一个不可变快照。
例如:
第一次部署:
GitHub main
content/posts
├── hello-world.mdx
└── nextjs.mdx
↓
Vercel Build
↓
Deployment A
↓
Function Runtime此时:
Function 中携带的是:
hello-world.mdx
nextjs.mdx之后:
GitHub:
content/posts
├── hello-world.mdx
├── nextjs.mdx
└── new-post.mdx发生变化。
但是:
GitHub Repository
↓
新的 commit不会自动修改:
Deployment A
↓
Function Runtime所以:
syncPosts()读取到的依然是:
旧文章列表最终自然得到:
no content changed正确的架构应该是什么?
发现问题之后,我重新调整了架构。
新的流程:
Pages CMS
↓
GitHub Commit
↓
GitHub Actions
↓
checkout 最新代码
↓
执行同步脚本
↓
更新数据库
↓
调用 Next.js API
↓
revalidateTag()
↓
ISR 更新缓存核心变化:
GitHub Actions 负责获取最新代码。
因为 Actions 才是真正拥有 Git 仓库工作区的环境。
GitHub Actions 为什么适合这个场景?
很多人会认为:
GitHub Actions 只适合重新构建项目。
其实并不是。
Actions 本质上就是一个 CI Runner。
它可以:
- 拉取代码;
- 执行脚本;
- 调用 API;
- 操作数据库。
因此:
这里不需要:
GitHub Push
↓
Vercel Build而是:
GitHub Push
↓
Actions
↓
同步数据
↓
触发 ISR避免了:
- 不必要的重新部署;
- 浪费构建时间;
- 增加部署成本。
什么是 ISR?
ISR:
Incremental Static Regeneration
中文:
增量静态再生。
它的目标:
在保持静态页面性能的同时,让页面可以动态更新。
SSR:每次请求生成页面
服务端渲染:
用户请求
↓
服务器查询数据
↓
生成 HTML
↓
返回浏览器优点:
- 数据实时。
缺点:
- 每次访问都有计算成本。
SSG:构建时生成页面
静态生成:
next build
↓
读取数据
↓
生成 HTML访问:
用户
↓
CDN
↓
HTML优点:
- 极快;
- 几乎没有服务器压力。
但是:
内容更新:
修改文章
↓
重新 build
↓
重新部署对于博客并不方便。
ISR:静态页面的局部更新
ISR 可以理解为:
让静态页面拥有重新生成能力。
流程:
第一次访问:
用户请求
↓
生成页面
↓
缓存之后:
用户请求
↓
读取缓存当内容发生变化:
触发重新验证
↓
旧缓存失效
↓
下一次请求重新生成整个过程:
不需要重新部署整个网站。
Next.js 中使用 ISR
在新版 Next.js 中,我使用:
Cache Components 模式
代替传统:
export const revalidate = 3600方式。
例如:
import { cacheLife, cacheTag } from 'next/cache'
export async function getPosts() {
'use cache'
cacheTag('posts')
cacheLife('weeks')
return await findPosts()
}use cache
'use cache'表示:
这个函数返回值应该被缓存。
cacheTag
cacheTag('posts')为缓存添加标签。
例如:
posts
├── 首页文章列表
├── RSS
└── 文章归档之后:
revalidateTag('posts')即可刷新所有相关缓存。
cacheLife
cacheLife('weeks')表示:
缓存生命周期。
博客内容通常不会每分钟变化,所以可以设置较长缓存。
但是:
我们仍然需要一种主动刷新机制。
这就是 Webhook。
什么是 Webhook?
Webhook 是一种:
事件驱动通知机制。
传统 API:
你的程序
↓
主动请求
↓
第三方服务例如:
每分钟询问:
有没有新文章?这种叫:
Polling(轮询)
缺点:
- 浪费请求;
- 有延迟。
Webhook:
内容变化
↓
主动发送 HTTP 请求
↓
通知目标系统例如:
CMS
文章更新
↓
Webhook
↓
你的服务我的博客如何使用 Webhook?
最终架构:
Pages CMS
↓
GitHub Commit
↓
GitHub Actions
↓
syncPosts()
↓
数据库更新
↓
POST /api/revalidate
↓
revalidateTag()
↓
ISR Cache 更新Next.js 接收刷新请求
Route Handler:
app/api/revalidate/posts/route.ts
示例:
import { revalidateTag } from 'next/cache'
export async function POST(req: Request) {
const auth = req.headers.get('authorization')
if (auth !== `Bearer ${process.env.WEBHOOK_SECRET}`) {
return new Response('Unauthorized', {
status: 401,
})
}
revalidateTag('posts', 'max')
return Response.json({
success: true,
})
}为什么需要鉴权?
Webhook 本质上是公开接口。
如果没有保护:
任何人都可以:
POST /api/revalidate/posts导致:
- 无限刷新缓存;
- 消耗服务器资源;
- 影响网站稳定性。
因此需要:
Authorization:
Bearer xxx进行验证。
为什么需要同步文章?
Webhook 本身只负责通知:
有内容变化。
它不会告诉系统:
哪篇文章变化。
因此:
收到通知后,需要执行同步逻辑:
syncPosts()例如:
返回:
;[
{
type: 'update',
slug: 'nextjs-isr',
},
{
type: 'delete',
slug: 'old-post',
},
]然后:
根据变化精准刷新缓存。
精准失效,而不是全部刷新
博客通常包含:
- 首页列表;
- 标签页面;
- 分类页面;
- 单篇文章;
- RSS。
如果每次:
revalidatePath('/')会导致大量页面重新计算。
所以使用:
Tag Cache。
例如:
posts
↓
文章列表
post-nextjs-isr
↓
单篇文章更新:
revalidateTag('post-nextjs-isr')只影响对应内容。
最终架构
┌──────────────┐
│ Pages CMS │
└──────┬───────┘
│
│ Git Commit
↓
┌──────────────┐
│ GitHub Action│
└──────┬───────┘
│
│ syncPosts()
↓
┌──────────────┐
│ Database │
└──────┬───────┘
│
│ Revalidate API
↓
┌──────────────┐
│ Next.js │
└──────┬───────┘
│
revalidateTag()
↓
┌──────────────┐
│ ISR Cache │
└──────┬───────┘
↓
最新博客页面总结
这次踩坑让我重新理解了 Serverless 平台的运行机制。
ISR 解决:
如何让静态页面拥有动态更新能力。
Webhook 解决:
如何让系统知道什么时候需要更新。
GitHub Actions 解决:
如何获取最新代码并执行同步任务。
最终:
- CMS 负责内容管理;
- GitHub 保存内容版本;
- Actions 执行同步任务;
- Next.js 管理缓存;
- ISR 提供高速访问。
形成了一套:
自动化、高性能、低成本的现代博客架构。
对于个人博客、文档站、知识库这类内容驱动型网站来说:
ISR + Webhook + CI Workflow
是一套非常优雅的解决方案。
它让我可以像使用动态网站一样维护博客,同时享受到静态网站接近 CDN 的访问速度。