NextJS+Strapi站点文章浏览量随机归零问题排查及优化咨询
Next.js 14 + Strapi 文章浏览量随机归零的原因及优化方案
我运营一个有数千篇文章的小型新闻站,前端用Next.js 14(pages目录),后端基于Strapi。每篇文章在Strapi里默认有个值为0的views数字字段,目前用以下代码实现浏览量递增:
export const Article = ({ data }) => { const [views, setViews] = useState() useEffect(() => { async function addViews() { setViews("loading") const article = await getArticle(data.id) if (article) { await updateArticle(data.id, article.views + 1) setViews(article.views + 1) } } addViews() }, [data]) return <article> <h1>{data.title}</h1> {(views && views !== "loading") && <span>{views}</span>} <div dangerouslySetInnerHTML={{ __html: data.content }} /> </article> }
当前功能整体正常,但部分文章的views字段会随机归零,想知道问题原因和优化方向。
问题原因
- 竞态条件与数值异常
当多个用户同时访问同一篇文章时,会同时触发getArticle请求,拿到相同的旧views值,再各自执行+1后更新。如果其中某次请求中article.views因接口异常返回undefined,undefined + 1会得到NaN,而Strapi的数字字段遇到非法值(如NaN)会自动重置为字段默认值0,导致浏览量归零。 - 重复触发更新
当dataprops发生变化(比如路由切换后重新渲染组件、页面刷新),useEffect会重复执行。如果某次重复执行时,getArticle返回的数据异常,同样可能导致非法值写入Strapi,触发归零。 - 缺乏错误处理
代码中没有对getArticle和updateArticle的请求错误做捕获,一旦请求失败或返回异常数据,会直接跳过更新逻辑,但如果请求返回了结构异常的article(比如views字段缺失),就会出现上述的NaN写入问题。
优化方案
1. 改用Strapi原子更新,避免竞态条件
不要在前端先查询再计算更新,直接让后端处理数值递增,从根源避免竞态和前端计算异常。
如果使用MongoDB数据库:
前端直接通过Strapi的更新接口传递$inc操作符:async function updateArticle(id) { await fetch(`/api/articles/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: { views: { $inc: 1 } } }) }); }如果使用SQL数据库(PostgreSQL/MySQL等):
在Strapi的文章控制器中自定义递增接口:// ./src/api/article/controllers/article.js module.exports = { async incrementViews(ctx) { const { id } = ctx.params; await strapi.db.query('api::article.article').update({ where: { id }, data: { views: strapi.db.raw('views + 1') } }); ctx.send({ success: true }); } };然后在路由配置中添加该接口,前端调用这个自定义接口完成递增:
async function updateArticle(id) { await fetch(`/api/articles/${id}/increment-views`, { method: 'POST' }); }
2. 防止重复触发更新
用useRef标记是否已经为当前文章执行过浏览量递增,避免同一用户多次刷新或路由切换导致的重复计数:
export const Article = ({ data }) => { const [views, setViews] = useState(data.views || 0) const hasIncremented = useRef(false) useEffect(() => { async function addViews() { if (hasIncremented.current) return hasIncremented.current = true try { await updateArticle(data.id) setViews(prev => prev + 1) } catch (err) { console.error('更新浏览量失败:', err) } } addViews() }, [data.id]) return <article> <h1>{data.title}</h1> <span>{views}</span> <div dangerouslySetInnerHTML={{ __html: data.content }} /> </article> }
3. 增加错误处理
在异步函数中加入try/catch捕获请求异常,同时对初始views值做兜底处理(比如默认取0),避免因数据异常导致的显示或更新错误。
4. 过滤爬虫请求
避免搜索引擎爬虫或自动化脚本触发浏览量计数,可以在前端或后端检查请求的User-Agent,只对真实用户的请求计数。
5. 后端幂等控制
在Strapi后端添加中间件,限制同一IP地址在短时间内(比如5分钟)对同一篇文章只能计数一次,进一步防止恶意刷量或重复计数。
内容的提问来源于stack exchange,提问作者Dolohov
相关产品推荐
相关产品推荐

