如何通过单次请求同时渲染页面并生成元数据(解决博客浏览量统计错误问题)
嘿,我完全懂你的困扰——多次请求或者缓存问题导致浏览量计数器乱跳,这对博客来说确实头疼。毕竟每多一次请求,后端的计数器就可能多算一次,咱们得想办法让元数据生成和页面渲染共用同一份请求数据,只发起一次后端调用。
先分析下你的现有代码:你现在的generateMetadata和页面组件CodeDetail各自调用了getTurkishPostBySlug,这就导致了两次独立的fetch请求,后端自然会统计两次浏览量。咱们的目标就是让这两处复用同一份请求结果,只触发一次后端接口调用。
方案1:利用Next.js自动请求去重(最简单)
Next.js在同一个请求周期内,会自动对相同URL、相同请求选项的fetch请求进行去重。也就是说,即使你在generateMetadata和组件里各调用一次getTurkishPostBySlug,只要请求的URL和参数完全一致,Next.js只会发起一次实际的后端请求。
不过为了确保这个机制生效,你需要保证getTurkishPostBySlug里的fetch选项完全一致(比如你已经设置了cache: "no-store",这没问题)。你的现有代码其实已经符合这个条件,但如果还是出现重复统计的情况,可以试试下面更稳妥的手动缓存方案。
方案2:手动添加请求级缓存(更可靠)
我们可以给getTurkishPostBySlug函数加一个请求级别的缓存,确保同一个请求周期内,相同slug的数据只会被获取一次。这里用Node.js的AsyncLocalStorage来实现请求隔离的缓存,避免并发请求之间的缓存冲突:
首先修改你的数据获取函数:
import { AsyncLocalStorage } from 'node:async_hooks'; // 创建一个异步本地存储,用于隔离每个请求的缓存 const requestStorage = new AsyncLocalStorage(); export async function getTurkishPostBySlug(TrSlug) { // 获取当前请求的缓存容器 let cache = requestStorage.getStore(); // 如果当前请求还没创建缓存容器,初始化一个 if (!cache) { cache = {}; return requestStorage.run(cache, async () => { return await fetchAndCacheData(TrSlug, cache); }); } // 如果缓存里已有数据,直接返回 if (cache[TrSlug]) { return cache[TrSlug]; } // 否则发起请求并缓存结果 return await fetchAndCacheData(TrSlug, cache); } async function fetchAndCacheData(TrSlug, cache) { const res = await fetch(`http://localhost:8080/post/${TrSlug}`, { cache: "no-store" }); let result; if (res.status === 200) { result = await res.json(); } else { result = "err"; } // 把结果存入当前请求的缓存 cache[TrSlug] = result; return result; }
然后你的page.js可以保持原来的写法,不需要大改:
import { getTurkishPostBySlug } from './path-to-your-function'; export async function generateMetadata({ params }) { const res = await getTurkishPostBySlug(params.slug); if (res.blog) { return { title: res.blog.TR_title, }; } // 提供默认元数据避免报错 return { title: "文章未找到" }; } async function CodeDetail({ params }) { const res = await getTurkishPostBySlug(params.slug); const post = res.blog || null; if (!post) { return <div>抱歉,该文章不存在或加载失败</div>; } // 这里渲染你的博客内容 return ( <article> <h1>{post.TR_title}</h1> {/* 其他博客内容,比如正文、发布时间等 */} </article> ); } export default CodeDetail;
为什么这个方案能解决问题?
现在无论generateMetadata和页面组件调用多少次getTurkishPostBySlug,在同一个用户请求中,只会发起一次实际的后端fetch请求,后端的浏览量计数器自然只会被触发一次,完美解决统计错误的问题。
备注:内容来源于stack exchange,提问作者BestSuperUser

