Commit 61dbca8d authored by Steven's avatar Steven

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
parent c45a5954
...@@ -50,7 +50,19 @@ func (*MetadataInterceptor) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc ...@@ -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
} }
} }
......
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 = ( ...@@ -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 = ( ...@@ -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 = ( ...@@ -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 }) { ...@@ -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
}); });
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment