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
相关产品推荐
相关产品推荐

