import { QueryClient } from "@tanstack/react-query"; import { createRouter as createTanStackRouter } from "@tanstack/react-router"; import { ApiError } from "./api/fetcher"; import { routeTree } from "./routeTree.gen"; function createQueryClient() { return new QueryClient({ defaultOptions: { queries: { staleTime: 2_000, // Don't hammer the host on auth/validation errors; do retry transient 5xx once. retry: (failureCount, error) => { if ( error instanceof ApiError && error.status >= 400 && error.status < 500 ) return false; return failureCount < 1; }, }, }, }); } /** * The browser's ONE QueryClient. * * `getRouter()` can run more than once per page load (hydration discards and rebuilds the tree), * and a fresh client each time means a fresh, empty cache that nothing else holds a reference to. * That is how the event stream ended up invalidating a cache no component was reading: the * subscription captured the client from the first router, the live pages read the second one, and * every invalidation went to the dead one. One client per browser session fixes that and keeps the * cache across a router rebuild. * * Deliberately browser-only: on the SERVER every request must get its OWN client, or one visitor's * data would be served from another's cache. */ let browserQueryClient: QueryClient | undefined; export function getRouter() { let queryClient: QueryClient; if (typeof window === "undefined") { queryClient = createQueryClient(); } else { if (!browserQueryClient) browserQueryClient = createQueryClient(); queryClient = browserQueryClient; } return createTanStackRouter({ routeTree, context: { queryClient }, defaultPreload: "intent", scrollRestoration: true, Wrap: ({ children }) => ( {children} ), }); } // Local import kept below the function so the module reads top-down. import { QueryClientProvider } from "@tanstack/react-query"; function QueryProvider({ client, children, }: { client: QueryClient; children: React.ReactNode; }) { return {children}; } declare module "@tanstack/react-router" { interface Register { router: ReturnType; } }