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

Next.js中URL更新异常:如何解决Loading initial props cancelled错误?

Next.js搜索栏快速输入触发getServerSideProps时的错误及解决方案探讨

问题场景

在Next.js项目中,搜索栏输入时通过router.push更新URL触发getServerSideProps,快速输入(比如连续输入"aaaaaaaaaaaaaa")会抛出Error: Loading initial props cancelled错误;持续输入或长按按键还会出现SyntaxError: Failed to execute 'measure' on 'Performance': The mark 'beforeRender' does not exist.错误。怀疑是同路径下使用router.push导致的Next.js Bug,纠结是否应该改用shallow routing避免触发getServerSideProps,转而通过API路由发起fetch请求。

报错信息

1. Loading initial props cancelled

next-dev.js?3515:20 Error: Loading initial props cancelled
    at eval (router.js?8684:1514:29)
window.console.error    @   next-dev.js?3515:20
overrideMethod  @   react_devtools_backend.js:4012
eval    @   router.js?8684:1064
asyncGeneratorStep  @   _async_to_generator.js?0e30:13
_next   @   _async_to_generator.js?0e30:31
eval    @   _async_to_generator.js?0e30:36
eval    @   _async_to_generator.js?0e30:28
handleRouteInfoError    @   router.js?8684:1110
eval    @   router.js?8684:1299
asyncGeneratorStep  @   _async_to_generator.js?0e30:13
_throw  @   _async_to_generator.js?0e30:34
Promise.then (async)        
asyncGeneratorStep  @   _async_to_generator.js?0e30:22
_next   @   _async_to_generator.js?0e30:31
Promise.then (async)        
asyncGeneratorStep  @   _async_to_generator.js?0e30:22
_next   @   _async_to_generator.js?0e30:31
Promise.then (async)        
asyncGeneratorStep  @   _async_to_generator.js?0e30:22
_next   @   _async_to_generator.js?0e30:31
eval    @   _async_to_generator.js?0e30:36
eval    @   _async_to_generator.js?0e30:28
getRouteInfo    @   router.js?8684:1301
eval    @   router.js?8684:821
asyncGeneratorStep  @   _async_to_generator.js?0e30:13
_next   @   _async_to_generator.js?0e30:31
Promise.then (async)        
asyncGeneratorStep  @   _async_to_generator.js?0e30:22
_next   @   _async_to_generator.js?0e30:31
Promise.then (async)        
asyncGeneratorStep  @   _async_to_generator.js?0e30:22
_next   @   _async_to_generator.js?0e30:31
eval    @   _async_to_generator.js?0e30:36
eval    @   _async_to_generator.js?0e30:28
change  @   router.js?8684:1034
push    @   router.js?8684:560
instance.<computed> @   router.js?31fc:147
eval    @   SearchBar.tsx?217d:54
commitHookEffectListMount   @   react-dom.development.js?ac89:23150
commitPassiveMountOnFiber   @   react-dom.development.js?ac89:24926
commitPassiveMountEffects_complete  @   react-dom.development.js?ac89:24891
commitPassiveMountEffects_begin @   react-dom.development.js?ac89:24878
commitPassiveMountEffects   @   react-dom.development.js?ac89:24866
flushPassiveEffectsImpl @   react-dom.development.js?ac89:27039
flushPassiveEffects @   react-dom.development.js?ac89:26984
commitRootImpl  @   react-dom.development.js?ac89:26935
commitRoot  @   react-dom.development.js?ac89:26682
performSyncWorkOnRoot   @   react-dom.development.js?ac89:26117
flushSyncCallbacks  @   react-dom.development.js?ac89:12042
flushSync   @   react-dom.development.js?ac89:26201
finishEventHandler  @   react-dom.development.js?ac89:3976
batchedUpdates  @   react-dom.development.js?ac89:3994
dispatchEventForPluginEventSystem   @   react-dom.development.js?ac89:9287
dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay @   react-dom.development.js?ac89:6465
dispatchEvent   @   react-dom.development.js?ac89:6457
dispatchDiscreteEvent

2. Performance measure 错误

Uncaught DOMException: Failed to execute 'measure' on 'Performance': The mark 'beforeRender' does not exist.
    at markRenderComplete (webpack-internal:///./node_modules/next/dist/client/index.js:313:17)
    at eval (webpack-internal:///./node_modules/next/dist/client/index.js:350:70)
    at Array.forEach (<anonymous>)
    at eval (webpack-internal:///./node_modules/next/dist/client/index.js:350:50)
    at commitHookEffectListMount (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:23145:26)
    at commitLayoutEffectOnFiber (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:23263:17)
    at commitLayoutMountEffects_complete (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:24683:9)
    at commitLayoutEffects_begin (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:24669:7)
    at commitLayoutEffects (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:24607:3)
    at commitRootImpl (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:26818:5)
    at commitRoot (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:26677:5)
    at finishConcurrentRender (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:25976:9)
    at performConcurrentWorkOnRoot (webpack-internal:///./node_modules/react-dom/cjs/react-dom.development.js:25804:7)
    at workLoop (webpack-internal:///./node_modules/scheduler/cjs/scheduler.development.js:266:34)
    at flushWork (webpack-internal:///./node_modules/scheduler/cjs/scheduler.development.js:239:14)
    at MessagePort.performWorkUntilDeadline (webpack-internal:///./node_modules/scheduler/cjs/scheduler.development.js:533:21)

相关代码

搜索栏组件(SearchBar.tsx)

export function parseQueryString(string: string | string[] | undefined) { // 辅助方法
  if (typeof string === 'string') {
    return string
  }
}

const router = useRouter()
const [search, setSearch] = useState(
  parseQueryString(router.query.search) ?? ''
)

useEffect(() => {
  if (search) {
    router.push({ query: { ...router.query, search } })
  } else {
    const { query } = router
    delete query.search
    router.push({ query })
  }
}, [search])

首页getServerSideProps逻辑

export const getServerSideProps: GetServerSideProps<Props> = async (
  context
) => {
  const { page } = context.query

  const skip = (parseQueryInt(page, 1) - 1) * NUM_ITEMS_PER_PAGE
  const search = parseQueryString(context.query.search)
    ?.split(' ')
    .filter((i) => i) // 过滤空字符串,避免出现"word1 " => "word1 & "的情况
    .join(' & ') // "word1 word2"转为"word1 & word2",符合PostgreSQL语法

  const numPages = Math.ceil(
    (await prisma.product.count({
      where: {
        title: { search },
      },
    })) / NUM_ITEMS_PER_PAGE
  )

  const products = await prisma.product.findMany({
    skip,
    take: NUM_ITEMS_PER_PAGE,
    where: {
      title: {
        search,
      },
    },
  })
  return {
    props: {
      products,
      numPages,
    },
  }
}

解决方案分析

1. 防抖处理(最直接的优化)

快速输入时频繁触发router.push,导致前一次的getServerSideProps请求被取消,这是报错的核心原因。给搜索输入添加防抖,延迟300-500ms再执行router.push,避免短时间内重复发起请求:

import { useEffect, useState } from 'react'
import { useRouter } from 'next/router'

export function parseQueryString(string: string | string[] | undefined) {
  if (typeof string === 'string') {
    return string
  }
}

export default function SearchBar() {
  const router = useRouter()
  const [search, setSearch] = useState(
    parseQueryString(router.query.search) ?? ''
  )

  useEffect(() => {
    const timer = setTimeout(() => {
      if (search) {
        router.push({ query: { ...router.query, search } }, undefined, { shallow: false })
      } else {
        const { query } = router
        delete query.search
        router.push({ query }, undefined, { shallow: false })
      }
    }, 300) // 300ms防抖延迟

    return () => clearTimeout(timer)
  }, [search, router])

  return (
    <input
      type="text"
      value={search}
      onChange={(e) => setSearch(e.target.value)}
      placeholder="搜索产品..."
    />
  )
}

2. 改用Shallow Routing + API路由

如果不想每次输入都触发服务器端渲染,可以用shallow routing更新URL(不会触发getServerSideProps),然后在客户端通过API路由异步获取数据:

  • 第一步:修改搜索栏的router.push,添加shallow: true:
useEffect(() => {
  const timer = setTimeout(() => {
    if (search) {
      router.push({ query: { ...router.query, search } }, undefined, { shallow: true })
    } else {
      const { query } = router
      delete query.search
      router.push({ query }, undefined, { shallow: true })
    }
  }, 300)

  return () => clearTimeout(timer)
}, [search, router])
  • 第二步:创建API路由(比如pages/api/search-products.ts):
import { NextApiRequest, NextApiResponse } from 'next'
import prisma from '../../lib/prisma'

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { page = 1, search } = req.query
  const NUM_ITEMS_PER_PAGE = 10

  const skip = (parseInt(page as string) - 1) * NUM_ITEMS_PER_PAGE
  const formattedSearch = typeof search === 'string'
    ? search.split(' ').filter(i => i).join(' & ')
    : undefined

  const numPages = Math.ceil(
    (await prisma.product.count({
      where: {
        title: { search: formattedSearch },
      },
    })) / NUM_ITEMS_PER_PAGE
  )

  const products = await prisma.product.findMany({
    skip,
    take: NUM_ITEMS_PER_PAGE,
    where: {
      title: { search: formattedSearch },
    },
  })

  res.status(200).json({ products, numPages })
}
  • 第三步:在首页组件中监听URL变化,调用API获取数据:
import { useEffect, useState } from 'react'
import { useRouter } from 'next/router'

type Props = {
  products: any[]
  numPages: number
}

export default function Home({ initialProducts, initialNumPages }: { initialProducts: any[], initialNumPages: number }) {
  const router = useRouter()
  const [products, setProducts] = useState(initialProducts)
  const [numPages, setNumPages] = useState(initialNumPages)
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    const fetchProducts = async () => {
      setLoading(true)
      const { search, page } = router.query
      const res = await fetch(`/api/search-products?search=${encodeURIComponent(search as string)}&page=${page || 1}`)
      const data = await res.json()
      setProducts(data.products)
      setNumPages(data.numPages)
      setLoading(false)
    }

    fetchProducts()
  }, [router.query])

  // 渲染产品列表...
}

// 保留getServerSideProps用于初始加载(可选,也可以直接客户端加载)
export const getServerSideProps: GetServerSideProps<Props> = async (context) => {
  // 初始加载逻辑和之前一致
}

3. 官方Bug相关说明

GitHub社区讨论提到,同路径下频繁调用router.push可能触发Next.js内部的请求取消逻辑,这属于框架层面的潜在问题。防抖或改用客户端数据获取能绕过这个问题,同时提升用户体验(减少不必要的服务器请求)。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 00:25:52