如何让Next.js(App Router)中Supabase缓存数据正确更新?
问题:Next.js App Router + Supabase 缓存更新不一致
我用Supabase搭配Next.js(App Router)开发全动态Web应用,全程禁用静态渲染。路由采用「父服务器组件(SSC)负责数据获取,子组件负责展示/交互/更新」的架构,多数场景运行正常,但某路由出现缓存更新异常:调用模态框的更新函数后,trips prop即时更新,users prop却需等待约30秒才会在ScheduleSheet的UI中刷新,模态框本身更新正常,且数据库已被正确修改。
架构简化说明
1. 父服务器组件
仅负责数据获取,代码如下:
export const dynamic = 'force-dynamic'; const page = async () => { const users = await getUsersWithTripsAndRoles(); const trips = await getTripsWithUsersAndRoles(); const tripTypes = await getActiveTripTypesWithTrips(); const profile = await getCurrentUser(); const roles = await getRoles(); if (!users) throw new Error('Error fetching users'); if (!trips) throw new Error('Error fetching trips'); if (!tripTypes) throw new Error('Error fetching users'); if (!roles) throw new Error('Error fetching roles'); if (!profile) throw new Error('Error fetching session'); return ( <ScheduleSheet users={users} trips={trips} tripTypes={tripTypes} profile={profile} roles={roles} /> ); };
2. 子组件处理函数
ScheduleSheet组件逻辑复杂,仅贴出数据变更相关代码(已确认数据库修改操作成功):
更新触发函数
位于ScheduleSheet的子模态框组件中,与ScheduleSheet同URL:
const updateFn = async ( person: NonNullable< Getters<typeof getTripsWithUsersAndRoles> >[0]['trip_to_user_to_role'][0]['user'], connect: boolean ) => { if (!person) throw new Error('update person not defined') if (allowedToEditTrips(profile)) { if (connect) addEmployeeToTrip(person.id, trip.id) if (!connect) removeEmployeeFromTrip(person.id, trip.id) router.refresh() } }
注:调用后模态框的props即时正确更新,可通过
tripsprop看到已添加的员工。
数据库操作函数
export async function addEmployeeToTrip(employeeId: string, tripId: string) { const supabase = createClientSupabaseClient() try { const companyId = await getCompanyId() const { error: insertError } = await supabase .from('trip_to_user_to_role') .insert({ user_id: employeeId, trip_id: tripId, company_id: companyId }) if (insertError) throw insertError return { success: true, error: null as PostgrestError | null } } catch (error) { console.error('Error:', error); return { success: false, error: error as PostgrestError | null } } }
Supabase客户端创建函数
export const createClientSupabaseClient = cache(() => createClientComponentClient<Database>() );
注:不清楚
cache()的作用,怀疑是否应该仅在服务器端代码中使用。
问题根源与修复方案
问题根源
- 客户端Supabase缓存冲突:
createClientComponentClient本身会在客户端维护查询缓存,用cache()包裹会复用同一客户端实例,导致users相关查询的缓存无法即时失效。 - 异步操作未等待:
updateFn中调用addEmployeeToTrip/removeEmployeeFromTrip时未加await,router.refresh()可能在数据库操作完成前触发,刷新时数据尚未同步。 - 查询维度的缓存差异:
getUsersWithTripsAndRoles与getTripsWithUsersAndRoles的查询维度不同(用户维度vs行程维度),两者的缓存失效机制不一致,导致更新后仅行程数据即时刷新。
修复方案
1. 移除客户端Supabase实例的cache()包裹
客户端组件无需缓存Supabase实例,直接创建新实例即可避免缓存复用:
export const createClientSupabaseClient = () => createClientComponentClient<Database>();
2. 强制服务器端查询跳过缓存
在getUsersWithTripsAndRoles中添加noCache(),强制Supabase跳过查询缓存:
// 示例:修改getUsersWithTripsAndRoles实现 async function getUsersWithTripsAndRoles() { const supabase = createServerComponentClient<Database>({ cookies }); const { data, error } = await supabase .from('users') .select('*, trip_to_user_to_role(*, trips(*))') .noCache(); // 强制跳过缓存 if (error) throw error; return data; }
3. 等待数据库操作完成后刷新路由
在updateFn中为异步操作添加await,确保数据库修改完成后再触发页面刷新:
const updateFn = async ( person: NonNullable< Getters<typeof getTripsWithUsersAndRoles> >[0]['trip_to_user_to_role'][0]['user'], connect: boolean ) => { if (!person) throw new Error('update person not defined') if (allowedToEditTrips(profile)) { // 等待数据库操作完成再刷新 if (connect) await addEmployeeToTrip(person.id, trip.id) if (!connect) await removeEmployeeFromTrip(person.id, trip.id) router.refresh() } }
4. 确认服务器端数据获取的缓存设置
确保所有服务器端Supabase查询未启用缓存,同时检查getUsersWithTripsAndRoles内部是否存在fetch缓存(即使设置了dynamic = 'force-dynamic',未显式设置cache: 'no-store'的fetch仍可能被缓存)。
内容的提问来源于stack exchange,提问作者A F
相关产品推荐
相关产品推荐

