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

NextJS 13路由二次重渲染崩溃,本地API数据获取异常求助

NextJS 13 路由切换返回后页面报错问题

我在NextJS 13中尝试从本地API获取数据,但切换到其他路由后返回该页面时出现错误,求原因。

错误截图:
错误截图

相关代码

page.tsx

export function DefaultSideBar() {
  return (
    <SideBar>
      <Logo></Logo>
      <SideOption src={AHlogo} title="Auction Browser"></SideOption>
    </SideBar>
  )
}

export default function Home() {
  
  return (
    <>
      <div className={styles.container}>
        <DefaultSideBar></DefaultSideBar>
        <MainPage disabled={false}></MainPage>
      </div>
    </>
    
  )
}
export function SideBar(props: {children: React.ReactNode}) {
    return (
        <div className={styles.sidebar}>
            {props.children}
        </div>
    )
}

MainPage 组件

export function MainPage(props: {disabled: boolean}) {
    return (
        <div className={styles["page"]}>
            <h1>Welcome Back!</h1>
            <hr />
            <h2>News</h2>
            <News></News>
        </div>
    )
}

News 组件

export function News() {
    'use client';

    let [outelems, setOutelems] = useState<JSX.Element[]>([])
    let dataref = useRef<NewsPanel[]>()
    dataref.current = use(getNewsData());
    useMemo(() => {
        console.log("ran")
        setOutelems((dataref.current ?? []).map(
            (d, index) => {
    
                return (
                    <a key={index} href={`/news/${index}`} className={styles["news-panel"]}>
                        <div >
                            <h3 className={styles["news-title"]}>{d.title}</h3>
                            <p className={styles["news-desc"]}><span dangerouslySetInnerHTML={{__html: formatDesc(d.description).split(" ").slice(0, 22).join(" ") + (formatDesc(d.description).split(" ").length > 22 ? "..." : "")}}></span> </p>
                            <p className={styles["news-time"]}>{timeConverter(parseInt(d.creation_date.toString()))}</p>
                        </div>
                    </a>
                    
                )
    
            }
        ).reverse())
    }, [])

    return (
        <div className={styles["news-scroll"]}>
            {outelems}
        </div>
    )
}

问题原因

  1. 非法使用use钩子:你在标记了'use client'的客户端组件里调用了React的use钩子,这个钩子是专为服务端组件设计的异步数据获取API,客户端环境下重复渲染(比如路由切换返回)时会触发React的运行时检查,直接报错。
  2. useMemo逻辑缺陷:useMemo的依赖项为空数组,只会执行一次,二次渲染时无法响应数据变化;同时结合错误的use钩子用法,数据获取逻辑完全失效,导致状态更新异常。

修复方案

方案一:客户端组件改用useEffect获取数据

把use钩子替换成useEffect结合异步函数,这是客户端组件数据获取的标准写法:

export function News() {
    'use client';

    const [outelems, setOutelems] = useState<JSX.Element[]>([])

    useEffect(() => {
        const fetchNews = async () => {
            const newsData = await getNewsData();
            const newsElements = (newsData ?? []).map((d, index) => (
                <a key={index} href={`/news/${index}`} className={styles["news-panel"]}>
                    <div>
                        <h3 className={styles["news-title"]}>{d.title}</h3>
                        <p className={styles["news-desc"]}>
                            <span dangerouslySetInnerHTML={{
                                __html: formatDesc(d.description).split(" ").slice(0,22).join(" ") + 
                                (formatDesc(d.description).split(" ").length>22 ? "..." : "")
                            }}></span>
                        </p>
                        <p className={styles["news-time"]}>{timeConverter(parseInt(d.creation_date.toString()))}</p>
                    </div>
                </a>
            )).reverse();
            setOutelems(newsElements);
        };

        fetchNews();
    }, []);

    return (
        <div className={styles["news-scroll"]}>
            {outelems}
        </div>
    )
}

方案二:拆分服务端+客户端组件

如果getNewsData适合在服务端执行(比如不需要浏览器环境),可以把数据获取逻辑移到服务端组件,只把渲染部分留在客户端:

// NewsServer.tsx(服务端组件,无需'use client')
export async function NewsServer() {
    const newsData = await getNewsData();
    return <NewsClient data={newsData ?? []} />;
}

// NewsClient.tsx(客户端组件)
'use client';
export function NewsClient({ data }: { data: NewsPanel[] }) {
    const outelems = useMemo(() => {
        return data.map((d, index) => (
            <a key={index} href={`/news/${index}`} className={styles["news-panel"]}>
                <div>
                    <h3 className={styles["news-title"]}>{d.title}</h3>
                    <p className={styles["news-desc"]}>
                        <span dangerouslySetInnerHTML={{
                            __html: formatDesc(d.description).split(" ").slice(0,22).join(" ") + 
                            (formatDesc(d.description).split(" ").length>22 ? "..." : "")
                        }}></span>
                    </p>
                    <p className={styles["news-time"]}>{timeConverter(parseInt(d.creation_date.toString()))}</p>
                </div>
            </a>
        )).reverse();
    }, [data]);

    return (
        <div className={styles["news-scroll"]}>
            {outelems}
        </div>
    )
}

之后在MainPage里替换成<NewsServer />即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 06:31:06