You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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字段会随机归零,想知道问题原因和优化方向。


问题原因

  1. 竞态条件与数值异常
    当多个用户同时访问同一篇文章时,会同时触发getArticle请求,拿到相同的旧views值,再各自执行+1后更新。如果其中某次请求中article.views因接口异常返回undefined,undefined + 1会得到NaN,而Strapi的数字字段遇到非法值(如NaN)会自动重置为字段默认值0,导致浏览量归零。
  2. 重复触发更新
    当data props发生变化(比如路由切换后重新渲染组件、页面刷新),useEffect会重复执行。如果某次重复执行时,getArticle返回的数据异常,同样可能导致非法值写入Strapi,触发归零。
  3. 缺乏错误处理
    代码中没有对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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 15:00:05