Next.js SSR页面返回后数据未更新的解决方案咨询
我的应用包含两个服务端渲染(SSR)页面:主页面用于展示用户账户统计数据,第二个页面可修改这些统计数据。进入主页面时能正常看到从数据库获取的统计数据;但跳转到第二个页面修改数据后,通过next/link的Link组件返回主页面,显示的仍是初始数据,页面似乎缓存了之前获取的数据。我考虑过将主页面改为客户端组件并使用useEffect(() => {}, [])钩子拉取数据,想请教是否存在其他可行方法?
主页面代码如下:
import Sidebar from "@/components/sidebar/component" import Header from "@/components/header/component" import StatisticsBoard from "@/components/statisticsBoard/component" import { getServerSession } from "next-auth" import { authOptions } from "@/app/api/auth/[...nextauth]/route" import { redirect } from "next/navigation" export default async function Home() { const session = await getServerSession(authOptions) if(!session) redirect("/") let loadedAllocations = (await (await fetch("http://localhost:3000/api/getAllocations", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId: session.user.id }) })).json()).allocations const loadedAllocaitonsGroups = (await (await fetch("http://localhost:3000/api/getAllocationsGroups", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId: session.user.id }) })).json()).allocationsGroups const loadedLedgerEntries = (await (await fetch("http://localhost:3000/api/getLedgerEntries", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId: session.user.id, orderBy: { date: "asc" } }) })).json()).ledgerEntries const loadedCategories = (await (await fetch("http://localhost:3000/api/getCategories", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId: session.user.id }) })).json()).categories let allocationsGroupsMap = {} for(const groupData of loadedAllocaitonsGroups) { if(!allocationsGroupsMap[groupData.id]) allocationsGroupsMap[groupData.id] = {} allocationsGroupsMap[groupData.id] = { name: groupData.name, allocationsData: [] } } for (const allocationData of loadedAllocations) { if (!allocationData.allocationsGroupId) continue allocationsGroupsMap[allocationData.allocationsGroupId].allocationsData.push(allocationData) } loadedAllocations = loadedAllocations.filter((value) => !value.allocationsGroupId) let incomesCategoriesMap = {} let costsCategoriesMap = {} let total = 0 let statistics = [] for(const category of loadedCategories) { if(category.type[0] == 'I') incomesCategoriesMap[category.id] = { name: category.name, total: 0 } else costsCategoriesMap[category.id] = { name: category.name, total: 0 } } for(let i = 0; i < loadedLedgerEntries.length; i++) { if(incomesCategoriesMap.hasOwnProperty(loadedLedgerEntries[i].categoryId)) { incomesCategoriesMap[loadedLedgerEntries[i].categoryId].total += loadedLedgerEntries[i].amount total += loadedLedgerEntries[i].amount } else { costsCategoriesMap[loadedLedgerEntries[i].categoryId].total += loadedLedgerEntries[i].amount total -= loadedLedgerEntries[i].amount } statistics.push({ date: loadedLedgerEntries[i].date, money: total }) } console.log("rendering server page..."); return ( <div className="top-0 left-0 flex flex-row h-screen" style={{ backgroundColor: "rgba(0, 0, 0, 0.5)", backgroundBlendMode: "multiply" }}> <Sidebar session={ session } loadedAllocations={ loadedAllocations } loadedAllocationsGroups={ Object.values(allocationsGroupsMap) }/> <div className="w-full m-3 flex flex-col"> <Header session={ session } /> <StatisticsBoard statistics={ statistics } incomesCategoriesMap={ incomesCategoriesMap} costsCategoriesMap={ costsCategoriesMap }/> </div> </div> ) }
解决方案1:禁用页面/请求的客户端缓存(保持SSR特性,推荐)
Next.js客户端导航时默认会缓存SSR页面的渲染结果,你可以通过以下两种方式强制每次获取最新数据:
方法A:给所有fetch请求添加cache: 'no-store'
直接在页面的fetch调用中设置缓存策略,告诉Next.js不要复用请求结果:
// 修改其中一个fetch请求,其余请求同理添加cache配置 let loadedAllocations = (await (await fetch("http://localhost:3000/api/getAllocations", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId: session.user.id }), cache: 'no-store' // 新增:禁用缓存 })).json()).allocations
这样每次服务器渲染页面时都会重新请求数据,不会使用缓存内容。
方法B:页面级别设置revalidate: 0
在页面组件外部导出revalidate变量,设置为0表示页面完全不缓存:
export const revalidate = 0; // 新增:页面每次请求都重新渲染 export default async function Home() { // ... 原页面代码 }
此方法会让整个页面每次被访问时都触发服务器渲染,适合实时性要求高的场景。
解决方案2:修改页返回时手动触发刷新
如果想保留页面缓存,但在数据修改后强制刷新主页面,可以在修改页完成数据保存后调用router.refresh():
'use client'; // 客户端组件标识 import { useRouter } from 'next/navigation'; export default function EditPage() { const router = useRouter(); const handleSave = async () => { // 执行数据修改逻辑 await updateStatistics(); // 跳转到主页面并强制刷新 router.push('/'); router.refresh(); }; return <button onClick={handleSave}>保存并返回</button>; }
这种方式不需要修改主页面的SSR逻辑,只在数据变更后主动触发刷新。
解决方案3:部分组件客户端化(混合SSR+客户端渲染)
如果不想整个主页面改为客户端组件,可以将数据展示部分(比如StatisticsBoard)改为客户端组件,在内部通过useEffect拉取最新数据:
'use client'; import { useEffect, useState } from 'react'; import { getServerSession } from 'next-auth/react'; import { authOptions } from "@/app/api/auth/[...nextauth]/route"; export default function StatisticsBoard() { const [statistics, setStatistics] = useState(null); const [incomesCategoriesMap, setIncomesMap] = useState(null); const [costsCategoriesMap, setCostsMap] = useState(null); useEffect(() => { async function fetchAndProcessData() { const session = await getServerSession(authOptions); if (!session) return; // 调用API获取数据 const [allocationsRes, groupsRes, ledgerRes, categoriesRes] = await Promise.all([ fetch("http://localhost:3000/api/getAllocations", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId: session.user.id }) }), fetch("http://localhost:3000/api/getAllocationsGroups", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId: session.user.id }) }), fetch("http://localhost:3000/api/getLedgerEntries", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId: session.user.id, orderBy: { date: "asc" } }) }), fetch("http://localhost:3000/api/getCategories", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userId: session.user.id }) }) ]); // 处理数据逻辑(和主页面一致) const loadedAllocations = (await allocationsRes.json()).allocations; const loadedAllocaitonsGroups = (await groupsRes.json()).allocationsGroups; const loadedLedgerEntries = (await ledgerRes.json()).ledgerEntries; const loadedCategories = (await categoriesRes.json()).categories; // ... 省略原页面的数据处理逻辑,最终得到statistics、incomesCategoriesMap、costsCategoriesMap // 处理完成后更新状态 setStatistics(processedStatistics); setIncomesMap(processedIncomesMap); setCostsMap(processedCostsMap); } fetchAndProcessData(); }, []); if (!statistics) return <div>加载中...</div>; // 原组件的渲染逻辑 return ( <div> {/* 展示统计数据 */} </div> ); }
修改后,主页面依然保持SSR,仅数据展示部分在客户端每次进入时重新拉取数据。
原因说明
Next.js的Link组件采用客户端导航(SPA模式),默认会缓存已渲染页面的内容,包括SSR页面的服务器渲染结果。因此从修改页返回主页面时,会直接复用缓存的旧页面,而不会重新触发服务器渲染获取最新数据。
内容的提问来源于stack exchange,提问作者MindW1n

