From df4fb1de747d7a7849003ca9f9aae9b47e1362ce Mon Sep 17 00:00:00 2001 From: kanywst Date: Thu, 7 May 2026 01:56:58 +0900 Subject: [PATCH 1/2] feat(cache): persist React Query state to localStorage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub's unauth API quota (60 req/h) is the practical bottleneck for this site, and a single hard refresh hits 10 languages × 1-2 queries. Wrap the app in PersistQueryClientProvider so successful responses survive reloads for 24h, falling back to a network request only when the entry is missing or stale. gcTime is bumped to match maxAge so entries are not evicted from the in-memory cache before they can be rehydrated. The buster string lets future schema changes invalidate stored caches without manual user cleanup. --- package-lock.json | 46 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 ++ src/App.tsx | 19 ++++++++++++++++--- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 14ac073..e6f1c33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,9 @@ "name": "stardust", "version": "0.0.0", "dependencies": { + "@tanstack/query-sync-storage-persister": "^5.100.9", "@tanstack/react-query": "^5.90.20", + "@tanstack/react-query-persist-client": "^5.100.9", "clsx": "^2.1.1", "lucide-react": "^1.14.0", "motion": "^12.38.0", @@ -1531,6 +1533,33 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tanstack/query-persist-client-core": { + "version": "5.100.9", + "resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-5.100.9.tgz", + "integrity": "sha512-sCPZZp3D9sOeqcA4SDxjUIm4wVq8PwHebH4ouFZetwjT4xvGjT/cLBQ4Sst+BFcFuk745pCPkf3T4MFliLHECQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.100.9" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-sync-storage-persister": { + "version": "5.100.9", + "resolved": "https://registry.npmjs.org/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-5.100.9.tgz", + "integrity": "sha512-v3cShfFtrxiLwc8b/fjQbUO4wIT//wLT5x4WWl8k/11s2ZyW9qpXE4xFWhiuAHCvKLluF0iI7ZnhT7BAlihwgg==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.100.9", + "@tanstack/query-persist-client-core": "5.100.9" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/react-query": { "version": "5.100.9", "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.9.tgz", @@ -1547,6 +1576,23 @@ "react": "^18 || ^19" } }, + "node_modules/@tanstack/react-query-persist-client": { + "version": "5.100.9", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-persist-client/-/react-query-persist-client-5.100.9.tgz", + "integrity": "sha512-qO7j+3VUZm4YH4T3dWszDqgvO+5+6NB1kSY+QmF7JD6+IONfeNmGyOzyobjmrX+6CYLPQtQ+sjM9vFYaSOAv6A==", + "license": "MIT", + "dependencies": { + "@tanstack/query-persist-client-core": "5.100.9" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.100.9", + "react": "^18 || ^19" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", diff --git a/package.json b/package.json index 3d84323..ddc5b18 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ "preview": "vite preview" }, "dependencies": { + "@tanstack/query-sync-storage-persister": "^5.100.9", "@tanstack/react-query": "^5.90.20", + "@tanstack/react-query-persist-client": "^5.100.9", "clsx": "^2.1.1", "lucide-react": "^1.14.0", "motion": "^12.38.0", diff --git a/src/App.tsx b/src/App.tsx index ff304bf..33fdf93 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,19 +1,29 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { QueryClient } from '@tanstack/react-query'; +import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'; +import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister'; import { languages } from './config/languages'; import { LanguageSection } from './components/LanguageSection'; import { Sparkles } from 'lucide-react'; import { motion } from 'motion/react'; import { GithubMark } from './components/icons/GithubMark'; +const ONE_DAY = 1000 * 60 * 60 * 24; + const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: false, retry: 1, + gcTime: ONE_DAY, }, }, }); +const persister = createSyncStoragePersister({ + storage: window.localStorage, + key: 'stardust-query-cache', +}); + const StarParticle = ({ delay, top, left }: { delay: number; top: string; left: string }) => ( +
{/* Background Nebula Effects */}
@@ -154,7 +167,7 @@ function App() {

- + ); } From 1814955347ae007a1d21eff6dcae730481216a50 Mon Sep 17 00:00:00 2001 From: kanywst Date: Thu, 7 May 2026 02:04:50 +0900 Subject: [PATCH 2/2] fix(cache): guard window.localStorage access against environments that throw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Older Safari private mode and browsers with site data disabled can throw on the very first `window.localStorage` property access (not just on getItem/setItem), which would have crashed the app at boot under PersistQueryClientProvider. Wrap the access in try/catch and fall back to undefined — createSyncStoragePersister treats that as 'no persistence' rather than crashing. --- src/App.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index 33fdf93..09e9dc4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,8 +19,16 @@ const queryClient = new QueryClient({ }, }); +const safeLocalStorage = (() => { + try { + return window.localStorage; + } catch { + return undefined; + } +})(); + const persister = createSyncStoragePersister({ - storage: window.localStorage, + storage: safeLocalStorage, key: 'stardust-query-cache', });