Next.js开发模式下静态博客文章内存缓存失效问题如何解决?
问题原因
- 开发模式下的模块热重载(HMR)机制:你使用的静态博客框架(Next.js、Astro、VitePress这类常见SSG工具都有该特性)在dev模式下默认会开启热替换,路由跳转、页面重载甚至项目文件无修改的场景下,框架都可能主动清除Node模块缓存、重新加载
post.ts模块,存在模块顶层的posts数组和postsGenerated标记会随模块重新执行被重置,你观察到的模块多次加载就是该机制导致的。 - 框架dev模式的执行逻辑:多数SSG框架在dev环境下不会复用服务端模块单例,每次触发页面渲染请求时都会重新执行相关服务端代码,模块不会像生产构建那样仅加载一次,顶层缓存自然无法复用。
- 现有代码存在竞态缺陷:你在进入else分支后立刻将
postsGenerated设为true,但此时所有文章的异步解析逻辑还未执行完成,如果此时有第二个请求进入,会直接返回还未填充完成的posts数组,也会表现为缓存失效。
优化方案
方案1:使用全局内存缓存绕开模块重载
把缓存挂载到不会随模块重载被重置的Node.jsglobal对象上,同时优化异步逻辑解决竞态问题,修改后的代码参考:
import fs from 'fs' import path from 'path' // ... // 全局挂载缓存容器 const globalForPosts = global as unknown as { posts: Post[] postsGenerated: boolean postsPromise?: Promise<Post[]> } // 初始化缓存,仅第一次加载模块时执行 if (!globalForPosts.posts) { globalForPosts.posts = [] globalForPosts.postsGenerated = false } const posts = globalForPosts.posts let postsGenerated = globalForPosts.postsGenerated let postsPromise = globalForPosts.postsPromise export async function getSortedPostsData(): Promise<Post[]> { // 已有缓存直接返回 if (postsGenerated && postsPromise) { console.log('+++++ Using cache ') return postsPromise } console.log('==== Calculating all') const pendings: Promise<any>[] = [] traverseDir('content/blog', (path) => { if (path.includes('.md')) { // 原有md文件解析逻辑,将异步任务push到pendings数组 } }) // 缓存整个异步任务,并发请求只会执行一次生成逻辑 postsPromise = Promise.all(pendings).then(() => { postsGenerated = true // 同步更新全局缓存标记 globalForPosts.postsGenerated = true globalForPosts.postsPromise = postsPromise return sortPostsByDate(posts) }) return postsPromise }
方案2:搭配文件监听实现缓存自动更新
如果需要修改md文件后自动刷新缓存,可以在dev模式下额外添加文件监听逻辑:
if (process.env.NODE_ENV === 'development') { fs.watch('content/blog', (eventType, filename) => { if (filename?.endsWith('.md')) { // 检测到md文件变化时重置缓存,下次请求自动重新生成 globalForPosts.posts = [] globalForPosts.postsGenerated = false globalForPosts.postsPromise = undefined console.log('博客内容更新,缓存已重置') } }) }
方案3:使用框架原生缓存能力
如果使用特定框架可以直接适配原生能力减少代码侵入:
- Next.js可以使用
unstable_cache方法包裹文章生成逻辑,或者把预处理逻辑放在generateStaticParams中,dev模式下默认会复用缓存 - Astro/VitePress可以在配置文件中提前预处理所有文章,把结果存在全局配置项中,页面直接读取配置即可
内容的提问来源于stack exchange,提问作者Nicolas Zozol
相关产品推荐
相关产品推荐

