Next.js 14 App Router数据变更后如何合理利用客户端路由缓存?
Next.js 14 AppRouter SSR 返回旧数据问题解决方案
问题场景
- 服务端组件从后端获取数组数据并传递给客户端组件
- 页面完成 hydration 后,用户修改某数据项状态并跳转到编辑页
- 数据变更同步到后端后,用户点击浏览器返回按钮,客户端路由仍展示旧数据,未从服务器重新获取
相关代码片段
服务端组件代码
'use server' export default async function ServerComponent() { const data = await fetchServerData(); return <ClientComponent arrayData={data}/> }
客户端列表组件代码
'use client' export function ClientComponent({arrayData}:Props) { return ( <ul> {arrayData.map(({status,text}) => (<Link to="/edit">{status} - {text}</Link>))} </ul>) }
编辑页组件代码
'use client' export function EditClientComponent() { function onSave() { // 发送请求修改数据项状态 } return <form onSubmit={onSave}>...</form> }
官方router.refresh()方案存在的问题:
- 页面可能出现闪烁
- 会导致客户端路由缓存完全失效,引发不必要的重复请求
解决方案
1. 编辑页保存后精准失效列表页缓存
在编辑页的保存逻辑中,仅在数据变更后触发列表页缓存刷新,避免列表页每次加载都刷新:
'use client' import { useRouter } from 'next/navigation'; import { revalidatePath } from 'next/cache'; export function EditClientComponent() { const router = useRouter(); async function onSave() { // 执行数据修改请求 await updateItemStatus(); // 精准失效列表页路由缓存 await revalidatePath('/list'); router.back(); } return <form onSubmit={onSave}>...</form> }
2. 给服务端请求添加缓存控制策略
在服务端组件的请求中设置缓存规则,控制数据的缓存时效:
'use server' export default async function ServerComponent() { // 禁用缓存,每次请求都拉取最新数据 const data = await fetchServerData({ next: { revalidate: 0 } }); // 或设置短缓存时间,比如5秒 // const data = await fetchServerData({ next: { revalidate: 5 } }); return <ClientComponent arrayData={data}/> }
如果是自定义请求函数,需确保内部fetch包含缓存配置:
async function fetchServerData(options = {}) { const res = await fetch('https://your-api-endpoint', { ...options, next: { revalidate: 0 } }); return res.json(); }
3. 客户端组件局部刷新数据
监听路由变化,仅在从编辑页返回时重新获取数据并更新组件状态,避免整页刷新:
'use client' import { useEffect, useState } from 'react'; import { usePathname, useRouter } from 'next/navigation'; export function ClientComponent({ arrayData: initialData }: Props) { const [arrayData, setArrayData] = useState(initialData); const pathname = usePathname(); const router = useRouter(); useEffect(() => { const fetchFreshData = async () => { if (pathname === '/list') { const freshData = await fetch('/api/list').then(res => res.json()); setArrayData(freshData); } }; // 组件挂载时检查路由状态 fetchFreshData(); // 监听浏览器返回事件 const unsubscribe = router.beforePopState(() => { fetchFreshData(); return true; }); return unsubscribe; }, [pathname, router]); return ( <ul> {arrayData.map(({ status, text, id }) => ( <Link key={id} to={`/edit/${id}`}>{status} - {text}</Link> ))} </ul> ); }
4. 使用RevalidateTag精准失效缓存
给服务端请求添加标签标记,数据变更后仅失效对应标签的缓存:
// 服务端组件 'use server' export default async function ServerComponent() { const data = await fetchServerData({ next: { tags: ['list-data'] } }); return <ClientComponent arrayData={data}/> } // 编辑页组件 'use client' import { revalidateTag } from 'next/cache'; import { useRouter } from 'next/navigation'; export function EditClientComponent() { const router = useRouter(); async function onSave() { await updateItemStatus(); // 仅失效标记为'list-data'的缓存 await revalidateTag('list-data'); router.back(); } return <form onSubmit={onSave}>...</form> }
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

