fix: prevent browser cache from serving stale memo data (#5470)
This fixes a critical data loss issue where users editing the same memo on multiple devices would overwrite each other's changes due to aggressive browser caching, particularly in Chromium-based browsers and PWAs. Changes: - Backend: Add Cache-Control headers to all API responses to prevent browser HTTP caching - Frontend: Force fresh fetch from server when opening memo editor by invalidating React Query cache - Frontend: Reduce memo query staleTime from 60s to 10s for better collaborative editing support Fixes #5470
This commit is contained in:
parent
c45a59549a
commit
61dbca8dc2
3 changed files with 22 additions and 3 deletions
|
|
@ -50,7 +50,19 @@ func (*MetadataInterceptor) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc
|
||||||
|
|
||||||
// Set metadata in context so services can use metadata.FromIncomingContext()
|
// Set metadata in context so services can use metadata.FromIncomingContext()
|
||||||
ctx = metadata.NewIncomingContext(ctx, md)
|
ctx = metadata.NewIncomingContext(ctx, md)
|
||||||
return next(ctx, req)
|
|
||||||
|
// Execute the request
|
||||||
|
resp, err := next(ctx, req)
|
||||||
|
|
||||||
|
// Prevent browser caching of API responses to avoid stale data issues
|
||||||
|
// See: https://github.com/usememos/memos/issues/5470
|
||||||
|
if resp != nil {
|
||||||
|
resp.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
resp.Header().Set("Pragma", "no-cache")
|
||||||
|
resp.Header().Set("Expires", "0")
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
|
import { memoKeys } from "@/hooks/useMemoQueries";
|
||||||
import type { Visibility } from "@/types/proto/api/v1/memo_service_pb";
|
import type { Visibility } from "@/types/proto/api/v1/memo_service_pb";
|
||||||
import type { EditorRefActions } from "../Editor";
|
import type { EditorRefActions } from "../Editor";
|
||||||
import { cacheService, memoService } from "../services";
|
import { cacheService, memoService } from "../services";
|
||||||
|
|
@ -13,6 +15,7 @@ export const useMemoInit = (
|
||||||
defaultVisibility?: Visibility,
|
defaultVisibility?: Visibility,
|
||||||
) => {
|
) => {
|
||||||
const { actions, dispatch } = useEditorContext();
|
const { actions, dispatch } = useEditorContext();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const initializedRef = useRef(false);
|
const initializedRef = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -24,6 +27,10 @@ export const useMemoInit = (
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (memoName) {
|
if (memoName) {
|
||||||
|
// Force refetch from server to prevent stale data issues
|
||||||
|
// See: https://github.com/usememos/memos/issues/5470
|
||||||
|
await queryClient.invalidateQueries({ queryKey: memoKeys.detail(memoName) });
|
||||||
|
|
||||||
// Load existing memo
|
// Load existing memo
|
||||||
const loadedState = await memoService.load(memoName);
|
const loadedState = await memoService.load(memoName);
|
||||||
dispatch(
|
dispatch(
|
||||||
|
|
@ -58,5 +65,5 @@ export const useMemoInit = (
|
||||||
};
|
};
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}, [memoName, cacheKey, username, autoFocus, defaultVisibility, actions, dispatch, editorRef]);
|
}, [memoName, cacheKey, username, autoFocus, defaultVisibility, actions, dispatch, editorRef, queryClient]);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export function useMemo(name: string, options?: { enabled?: boolean }) {
|
||||||
return memo;
|
return memo;
|
||||||
},
|
},
|
||||||
enabled: options?.enabled ?? true,
|
enabled: options?.enabled ?? true,
|
||||||
staleTime: 1000 * 60, // 1 minute - memos can be edited frequently
|
staleTime: 1000 * 10, // 10 seconds - reduced to prevent stale data in collaborative editing
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue