diff --git a/apps/nextjs/src/app/[locale]/_client-providers/query-persistence.spec.ts b/apps/nextjs/src/app/[locale]/_client-providers/query-persistence.spec.ts index e84b12848..79003ea6d 100644 --- a/apps/nextjs/src/app/[locale]/_client-providers/query-persistence.spec.ts +++ b/apps/nextjs/src/app/[locale]/_client-providers/query-persistence.spec.ts @@ -42,6 +42,9 @@ describe("dashboard query persistence", () => { const supporting = createSuccessfulQuery([["app", "byIds"], { type: "query" }], [{ id: "app-1" }]); const ping = createSuccessfulQuery([["widget", "app", "ping"], { type: "query" }], { status: "online" }); const beszel = createSuccessfulQuery([["widget", "beszel", "getSystemStats"], { type: "query" }], []); + const customApi = createSuccessfulQuery([["widget", "customApi", "getData"], { type: "query" }], { + apiKey: "secret", + }); const unrelated = createSuccessfulQuery([["board", "getBoardByName"], { type: "query" }], { name: "Home" }); const pendingClient = new QueryClient(); const pending = pendingClient.getQueryCache().build(pendingClient, { @@ -55,6 +58,7 @@ describe("dashboard query persistence", () => { expect(shouldPersistDashboardQuery(supporting.query)).toBe(true); expect(shouldPersistDashboardQuery(ping.query)).toBe(false); expect(shouldPersistDashboardQuery(beszel.query)).toBe(false); + expect(shouldPersistDashboardQuery(customApi.query)).toBe(false); expect(shouldPersistDashboardQuery(unrelated.query)).toBe(false); expect(shouldPersistDashboardQuery(pending)).toBe(false); expect(shouldPersistDashboardQuery(failed.query)).toBe(true); diff --git a/apps/nextjs/src/app/[locale]/_client-providers/query-persistence.ts b/apps/nextjs/src/app/[locale]/_client-providers/query-persistence.ts index 905fbb839..77dc9902b 100644 --- a/apps/nextjs/src/app/[locale]/_client-providers/query-persistence.ts +++ b/apps/nextjs/src/app/[locale]/_client-providers/query-persistence.ts @@ -4,7 +4,7 @@ import { removeOldestQuery } from "@tanstack/react-query-persist-client"; import type { PersistQueryClientProviderProps } from "@tanstack/react-query-persist-client"; import { parse, stringify } from "superjson"; -import { isWidgetDataQueryKey, queryCacheDefaultGcTimeMs } from "@homarr/api/query-cache"; +import { isPersistableDashboardQueryKey, queryCacheDefaultGcTimeMs } from "@homarr/api/query-cache"; export const queryPersistenceBuster = "v5-dashboard-data"; @@ -19,7 +19,7 @@ interface PersistableQuery { } export const shouldPersistDashboardQuery = (query: PersistableQuery) => - query.state.data !== undefined && isWidgetDataQueryKey(query.queryKey); + query.state.data !== undefined && isPersistableDashboardQueryKey(query.queryKey); const getSessionStorage = () => { if (typeof window === "undefined") return undefined; diff --git a/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx b/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx index 50dd85d7b..5b6621679 100644 --- a/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx +++ b/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx @@ -114,6 +114,7 @@ const ScopedTRPCReactProvider = ({ queryCache: new QueryCache({ onError(error, query) { if (!isTrpcForbiddenError(error) || !isWidgetDataQueryKey(query.queryKey)) return; + void queryPersistence.persister.removeClient(); query.setState({ data: undefined, dataUpdatedAt: 0, isInvalidated: true }); }, }), diff --git a/packages/api/src/query-cache.ts b/packages/api/src/query-cache.ts index 7f77b7fbe..3ef88895b 100644 --- a/packages/api/src/query-cache.ts +++ b/packages/api/src/query-cache.ts @@ -19,6 +19,7 @@ export const queryCacheDefaultGcTimeMs = 1000 * 60 * 5; export const queryCacheMetadataStaleTimeMs = 1000 * 60 * 5; const widgetDataQueryPaths = new Set(["app.byId", "app.byIds", "docker.getContainers", "integration.byIds"]); +const persistenceExcludedWidgetPaths = new Set(["widget.customApi.getData", "widget.customApi.queryRequest"]); // Beszel system stats are large time-series payloads and were deliberately kept out of // the persisted cache in #6289; live stats come from an SSE subscription instead. const excludedWidgetPaths = new Set(["widget.app.ping", "widget.beszel.getSystemStats"]); @@ -77,3 +78,11 @@ export const isWidgetDataQueryKey = (queryKey: QueryKey) => { return isWidgetDataTrpcPath(path.join(".")); }; + +export const isPersistableDashboardQueryKey = (queryKey: QueryKey) => { + const path = getTrpcPathFromQueryKey(queryKey); + if (!path) return false; + + const joinedPath = path.join("."); + return isWidgetDataTrpcPath(joinedPath) && !persistenceExcludedWidgetPaths.has(joinedPath); +};