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

Next.js+getServerSideProps下避免router.asPath对应useEffect首次重复渲染

问题原因

基于getServerSideProps的Next.js Pages Router页面首次加载时,router.asPath会触发两次变更:

  • 第一次是服务端直出HTML完成初始渲染时,路由对象初始化触发asPath更新
  • 第二次是客户端水合完成、路由系统完全激活时,会再次同步路由状态触发asPath更新
    两次变更都会触发绑定了[router.asPath]依赖的useEffect执行,最终导致重复的数据请求。
解决思路

方案1(推荐,符合Next.js设计逻辑):首屏数据走服务端预取,客户端只处理后续路由变更

既然已经使用getServerSideProps,完全可以把首屏的参数解析、数据拉取逻辑迁移到服务端执行,从根源避免首屏重复请求:

  • 在getServerSideProps内直接从context中解析query参数,在服务端完成数据库查询,将解析后的筛选条件、首屏数据通过props传递给页面组件
  • 页面组件初始化时,直接将props中的筛选条件作为filtersData的初始值,首屏直接渲染服务端返回的预取数据,不需要在首屏执行客户端拉取
  • 组件内增加首屏挂载标记,跳过首次effect执行,只在用户操作筛选器触发客户端路由跳转时,才执行客户端参数解析和数据拉取
    核心实现参考:
import { useRef, useState } from 'react'
import { useRouter } from 'next/router'

export default function ExplorePage({ initialFilters, initialData }) {
  const router = useRouter()
  // 首屏挂载标记
  const isFirstLoad = useRef(true)
  // 初始化状态直接用服务端下发的数据
  const [filtersData, setFiltersData] = useState(initialFilters)
  const [listData, setListData] = useState(initialData)
  const [initialLoading, setInitialLoading] = useState(false)

  useEffect(() => {
    // 首屏由getServerSideProps已经拿到数据,直接跳过
    if (isFirstLoad.current) {
      isFirstLoad.current = false
      return
    }

    // 直接用router.query拿解析好的参数,不用自己手写解析逻辑
    const result = router.query
    const queryParams = []

    // 合并为一次setState,避免多次重渲染
    const newFilters = {
      ...filtersData,
      thumbType: result.type
    }

    if (result.field) {
      newFilters.categoryField = result.field
      newFilters.categoryOperator = result.fieldOp
      newFilters.categoryValue = result.fieldVal
      queryParams.push(where(result.field, result.fieldOp, decodeURIComponent(result.fieldVal)))
    }

    if (result.orderBy) {
      newFilters.orderFieldActions = result.orderBy
      newFilters.orderOperatorActions = result.orderType
      queryParams.push(orderBy(result.orderBy, result.orderType))
    }

    setFiltersData(newFilters)
    setSearchParams(queryParams)
    getFilteredData(result.type).then(res => setListData(res))
    setInitialLoading(false)
  }, [router.asPath])

  // 页面渲染逻辑
}

export async function getServerSideProps(context) {
  // 在这里解析query、查库,返回初始数据给页面
  const { type, category, field, fieldOp, fieldVal, orderBy, orderType } = context.query
  // 执行对应数据库查询逻辑拿到initialData
  // 组装初始筛选参数initialFilters
  return {
    props: {
      initialFilters,
      initialData
    }
  }
}

方案2:请求参数去重(不调整getServerSideProps逻辑的快速修复方案)

如果暂时不想迁移逻辑到服务端,可以通过缓存已请求的参数标识,两次effect触发时如果参数完全一致,直接跳过重复请求:

const lastRequestQuery = useRef('')

useEffect(() => {
  const result = router.query // 替换自己手写的解析逻辑
  // 生成当前请求参数的唯一标识
  const currentQueryKey = JSON.stringify(result)
  // 和上一次请求的参数对比,完全一致就终止执行
  if (currentQueryKey === lastRequestQuery.current) return
  lastRequestQuery.current = currentQueryKey

  // 原有参数更新、数据拉取逻辑
  // ...
}, [router.asPath])
额外优化建议
  • 不要手动解析location.search,Next.js的router.query已经内置了完整的参数解析能力,能覆盖编码异常、空参数、多值参数等边界场景
  • 不要在同一个effect内多次调用setFiltersData,多次状态更新会触发不必要的组件重渲染,将所有状态变更合并为一次setState调用即可
  • 数据请求逻辑建议增加中断控制,用户快速切换筛选条件时取消上一次未完成的请求,避免旧请求返回覆盖最新的渲染结果

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 00:24:37