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

Next.js集成Sanity时Patch请求耗时超2秒,求排查方案

Sanity Patch请求响应耗时超2秒的排查与优化

我搞不懂为什么简单的Sanity Patch请求服务器响应要花2秒多。场景是卡片右滑时触发saveLike和saveVote两个操作,都从Card.js获取卡片信息和用户ID来存储。用Next.js集成Sanity,分别用yarn dev和yarn start运行项目。另外,长期来看,能指导我调试这类查询耗时问题的思路也很重要。


上下文代码

handleRightSwipe函数

const handleRightSwipe = async (cardData, votesData) => {
    try {
      await fetch('/api/saveLike', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body:  JSON.stringify({
          likedCards: cardData,
          currentUser: "abc123",
        }),
      })
      // 递减投票数
      let votesAmount = votesData--
      try {
        await fetch('/api/saveVote', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body:  JSON.stringify({
          _id: "abc123",
          votes: votesAmount
        }),
      })
    } catch (error) {
      console.error(error)
    }
      // 更新客户端投票数
      setVotesData(votesAmount)
      // 请求最新投票数
      requestVoteAmount(userID)
    } catch (error) {
      console.error(error)
    }
  }

saveVote API路由

import { client } from '../../lib/sanity'

const saveVote = async (req, res) => {
  try {
    await client
      .patch(req.body._id).set({votes:req.body.votes}).commit()
    res.status(200).send({ message: 'success' })
  } catch (error) {
    res.status(500).send({ message: 'error', data: error.message })
  }
}

export default saveVote

saveLike API路由

import { client } from '../../lib/sanity'

const saveLike = async (req, res) => {
  try {
    await client
      .patch(req.body.likedCards._id)
      .setIfMissing({'likes': []})
      .insert('after', 'likes[-1]',[
        {
          _ref: req.body.currentUser,
          _type: "reference",
        },
      ])
      .commit({
        autoGenerateArrayKeys: true,
      })
    res.status(200).send({ message: 'success' })
  } catch (error) {
    res.status(500).send({ message: 'error', data: error.message })
  }
}

Card组件代码

import React, { useState, useMemo, useContext, useRef, useEffect } from 'react'
import { Context} from '../context/Context'
import TCard from 'react-tinder-card'

const Card = () => {
  const { cardsData, votesData } = useContext(Context)
  const { handleRightSwipe, handleLeftSwipe, currentAccountAddress } = useContext(Context)
  const [currentIndex, setCurrentIndex] = useState(cardsData.length - 1)
  const currentIndexRef = useRef(currentIndex)
  const childRefs = useMemo(
    () =>
      Array(cardsData.length)
        .fill(0)
        .map((i) => React.createRef()),
    [cardsData.length]
  )

  const updateCurrentIndex = (val) => {
    setCurrentIndex(val)
    currentIndexRef.current = val
  }
const canVote = votesData > 0
const swiped = (direction, card, index) => {
  if (!canVote) return
  if (votesData > 0) {
    if (direction === 'right') {
      const voterAmount = votesData--
      updateCurrentIndex(index - 1)
      handleRightSwipe(card, currentAccountAddress, voterAmount)
    }
    if (direction === 'left') {
      const voterAmount = votesData--
      updateCurrentIndex(index - 1)
      handleLeftSwipe(card, currentAccountAddress, voterAmount)
    }
  }
}
const outOfFrame = (idx) => {
  currentIndexRef.current >= idx && childRefs[idx].current.restoreCard()
}
  return (
    <div>
      <div>
        {cardsData.map((card, index) => (
          <TCard 
            card={card}
            key={card.name}
            ref={childRefs[index]}
            preventSwipe={['up', 'down']}
            onCardLeftScreen={() => outOfFrame(card.name, index)}
            onSwipe={(dir) => swiped(dir, card, index)}>
              <div
              style={{ backgroundImage: `url('${card.imageUrl}')` }}>
              </div>
          </TCard> ))}
      </div>
    </div>    
  )
}
export default Card

即时优化建议

  1. 并行请求替代串行请求
    当前saveLike和saveVote是串行执行,总耗时为两个请求时间之和。改成并行可直接减少总耗时,同时修正原代码中votesData--的逻辑错误(后置递减会导致传递旧值):
const handleRightSwipe = async (cardData, votesData) => {
  try {
    const votesAmount = votesData - 1
    // 并行发起两个请求
    await Promise.all([
      fetch('/api/saveLike', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ likedCards: cardData, currentUser: "abc123" })
      }),
      fetch('/api/saveVote', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ _id: "abc123", votes: votesAmount })
      })
    ])
    setVotesData(votesAmount)
    // 若无多客户端并发修改场景,可去掉requestVoteAmount减少额外请求
    // requestVoteAmount(userID)
  } catch (error) {
    console.error(error)
  }
}
  1. 优化Sanity客户端配置
    检查lib/sanity.js中的客户端配置,确保apiVersion为最新稳定版,生产环境开启useCdn: true(读写操作不受CDN影响,但可优化客户端初始化路径),同时选择与服务器地理位置接近的Sanity区域,减少网络延迟。

  2. 移除不必要的客户端刷新
    若没有多客户端同时修改投票数的场景,requestVoteAmount(userID)可直接用本地更新的votesAmount替代,避免额外网络请求耗时。


长期调试耗时问题的思路

  • 拆分测量各阶段耗时
    在客户端和API路由中添加计时,精准定位耗时环节:

    // 客户端测量请求耗时
    console.time('saveLike-request')
    await fetch('/api/saveLike', ...)
    console.timeEnd('saveLike-request')
    
    // API路由中测量Sanity操作耗时
    const saveVote = async (req, res) => {
      try {
        console.time('sanity-patch-vote')
        await client.patch(req.body._id).set({votes:req.body.votes}).commit()
        console.timeEnd('sanity-patch-vote')
        res.status(200).send({ message: 'success' })
      } catch (error) {
        res.status(500).send({ message: 'error', data: error.message })
      }
    }
    
  • Sanity Studio监控
    进入Sanity Studio的Dataset > Activity面板,查看Patch操作的执行时间,确认是Sanity服务端还是网络环节导致的延迟。

  • 对比开发/生产模式差异
    yarn dev模式下Next.js有热重载等额外开销,对比yarn start生产模式的耗时,判断是否为开发环境特有问题。

  • 网络层排查
    用浏览器开发者工具的Network面板,查看请求的DNS解析、TCP连接、等待响应等各阶段耗时,针对性优化网络链路。

  • Sanity数据结构优化
    对于likes数组插入操作,可考虑调整数据结构:比如将用户的喜欢列表存储在用户文档中(而非卡片文档),根据查询频率选择更高效的存储方式,减少Patch操作的锁竞争或数据量。

内容的提问来源于stack exchange,提问作者askmeaquestion1234

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 23:15:36