From 8b3df5bd484060acac4d926c8d25334ca244c7af Mon Sep 17 00:00:00 2001 From: Aly Raffauf Date: Fri, 24 Apr 2026 13:36:05 -0400 Subject: [PATCH] web: persist cache with react-query-persist-client --- web/package-lock.json | 46 +++++++++++++++++++++++++++++++++++ web/package.json | 2 ++ web/src/lib/queryPersister.ts | 26 ++++++++++++++++++++ web/src/main.tsx | 10 +++++--- 4 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 web/src/lib/queryPersister.ts diff --git a/web/package-lock.json b/web/package-lock.json index 77290be..209447e 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -11,8 +11,10 @@ "@atcute/atproto": "^3.1.11", "@atcute/client": "^4.2.1", "@atcute/oauth-browser-client": "^3.0.0", + "@tanstack/query-sync-storage-persister": "^5.100.1", "@tanstack/react-query": "^5.100.1", "@tanstack/react-query-devtools": "^5.100.1", + "@tanstack/react-query-persist-client": "^5.100.1", "lucide-react": "^1.8.0", "react": "^19.2.5", "react-dom": "^19.2.5", @@ -1358,6 +1360,33 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tanstack/query-persist-client-core": { + "version": "5.100.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-5.100.1.tgz", + "integrity": "sha512-zopD/OxWWjKUBmB8ydTzz9LBw/MGRz9vbdMV/DFxlMFTg9bP+QiBAMDgdWYpswhdjGlixbjml8mTD9HX4v1Uww==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.100.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-sync-storage-persister": { + "version": "5.100.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-5.100.1.tgz", + "integrity": "sha512-6lSgoZy+Vqgr0tQjQu4dL0af86bRi2mN94k4yMXVI1EFevY4gPYpKM/PIMbdE0qDVP/CqBALG2zaNTIJtgHj8g==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.100.1", + "@tanstack/query-persist-client-core": "5.100.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/react-query": { "version": "5.100.1", "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.1.tgz", @@ -1391,6 +1420,23 @@ "react": "^18 || ^19" } }, + "node_modules/@tanstack/react-query-persist-client": { + "version": "5.100.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-persist-client/-/react-query-persist-client-5.100.1.tgz", + "integrity": "sha512-9APCLGDNoNkJmFFdQ5nvRrQMwZr2IjD3cpF8g+2WRs2QVehpRotxg5gzKgTFOaiRKBs8/FWbyiNjyZ7Vkp0aTg==", + "license": "MIT", + "dependencies": { + "@tanstack/query-persist-client-core": "5.100.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.100.1", + "react": "^18 || ^19" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", diff --git a/web/package.json b/web/package.json index e9d4fac..67290a4 100644 --- a/web/package.json +++ b/web/package.json @@ -12,8 +12,10 @@ "@atcute/atproto": "^3.1.11", "@atcute/client": "^4.2.1", "@atcute/oauth-browser-client": "^3.0.0", + "@tanstack/query-sync-storage-persister": "^5.100.1", "@tanstack/react-query": "^5.100.1", "@tanstack/react-query-devtools": "^5.100.1", + "@tanstack/react-query-persist-client": "^5.100.1", "lucide-react": "^1.8.0", "react": "^19.2.5", "react-dom": "^19.2.5", diff --git a/web/src/lib/queryPersister.ts b/web/src/lib/queryPersister.ts new file mode 100644 index 0000000..8b29766 --- /dev/null +++ b/web/src/lib/queryPersister.ts @@ -0,0 +1,26 @@ +import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister"; + +// Bump when cache shapes change (lexicon edits, query-key restructures) so +// older clients discard incompatible cached data on next load instead of +// deserializing into crashes. +const BUSTER = "atbbs-v1"; +const MAX_AGE = 24 * 60 * 60 * 1000; + +const persister = createSyncStoragePersister({ + storage: localStorage, + key: "atbbs:query-cache", +}); + +export const persistOptions = { + persister, + buster: BUSTER, + maxAge: MAX_AGE, + dehydrateOptions: { + // Skip fingerprinted thread-page entries. Their keys churn whenever a + // reply is added or deleted, so persisting them just bloats localStorage + // with old-fingerprint garbage. thread-refs is persisted and drives the + // page rebuild on load. + shouldDehydrateQuery: (query: { queryKey: readonly unknown[] }) => + query.queryKey[0] !== "thread-page", + }, +}; diff --git a/web/src/main.tsx b/web/src/main.tsx index dbb0882..159e2a1 100644 --- a/web/src/main.tsx +++ b/web/src/main.tsx @@ -1,9 +1,10 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { RouterProvider } from "react-router-dom"; -import { QueryClientProvider } from "@tanstack/react-query"; +import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { queryClient } from "./lib/queryClient"; +import { persistOptions } from "./lib/queryPersister"; import { router } from "./router/routes"; import { BreadcrumbProvider } from "./hooks/useBreadcrumb"; import "./index.css"; @@ -14,13 +15,16 @@ window.addEventListener("pageshow", (event) => { createRoot(document.getElementById("root")!).render( - + {import.meta.env.DEV && ( )} - + , ); -- 2.51.2