perf: implement LRU-cached persistent feed mounting to eliminate Virtuoso flash

Keep Board/Catalog feeds mounted outside React Router's control to prevent
Virtuoso from remounting when navigating to/from post pages. The feed is
hidden via CSS when viewing posts and shown again when returning, eliminating
the visible flash/displacement that occurred during scroll restoration.

- Add FeedCacheContainer to manage LRU cache of 2 feeds
- Modify Board/Catalog to accept cache props and visibility state
- Add route utilities for feed/post route detection
This commit is contained in:
plebeius
2025-12-08 22:40:16 +01:00
parent 6fe4116c6b
commit 896ca4ad13
8 changed files with 376 additions and 71 deletions
+54
View File
@@ -0,0 +1,54 @@
import { create } from 'zustand';
export interface CachedFeed {
key: string;
type: 'board' | 'catalog';
lastAccessed: number;
}
interface FeedCacheState {
cachedFeeds: CachedFeed[];
maxCacheSize: number;
accessFeed: (key: string, type: 'board' | 'catalog') => void;
removeFeed: (key: string) => void;
isFeedCached: (key: string) => boolean;
}
const useFeedCacheStore = create<FeedCacheState>((set, get) => ({
cachedFeeds: [],
maxCacheSize: 2,
accessFeed: (key: string, type: 'board' | 'catalog') => {
const { cachedFeeds, maxCacheSize } = get();
const now = Date.now();
const existingIndex = cachedFeeds.findIndex((feed) => feed.key === key);
if (existingIndex !== -1) {
const updatedFeeds = [...cachedFeeds];
updatedFeeds[existingIndex] = { ...updatedFeeds[existingIndex], lastAccessed: now };
set({ cachedFeeds: updatedFeeds });
} else {
const newFeed: CachedFeed = { key, type, lastAccessed: now };
let updatedFeeds = [...cachedFeeds, newFeed];
if (updatedFeeds.length > maxCacheSize) {
updatedFeeds.sort((a, b) => a.lastAccessed - b.lastAccessed);
updatedFeeds = updatedFeeds.slice(1);
}
set({ cachedFeeds: updatedFeeds });
}
},
removeFeed: (key: string) => {
const { cachedFeeds } = get();
set({ cachedFeeds: cachedFeeds.filter((feed) => feed.key !== key) });
},
isFeedCached: (key: string) => {
const { cachedFeeds } = get();
return cachedFeeds.some((feed) => feed.key === key);
},
}));
export default useFeedCacheStore;