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

如何让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即时正确更新,可通过trips prop看到已添加的员工。

数据库操作函数

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()的作用,怀疑是否应该仅在服务器端代码中使用。

问题根源与修复方案

问题根源

  1. 客户端Supabase缓存冲突:createClientComponentClient本身会在客户端维护查询缓存,用cache()包裹会复用同一客户端实例,导致users相关查询的缓存无法即时失效。
  2. 异步操作未等待:updateFn中调用addEmployeeToTrip/removeEmployeeFromTrip时未加await,router.refresh()可能在数据库操作完成前触发,刷新时数据尚未同步。
  3. 查询维度的缓存差异: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 13:23:27