'use client'; import { QueryClient } from '@tanstack/react-query'; import { ApiError } from './api-client'; /** * 401/403 errors are expected (logged-out users hit protected endpoints during * initial render or token expiry) and should stay in the query's error state — * components can show a "please sign in" placeholder. Do NOT propagate them to * the error boundary, which would unmount the whole dashboard and spam the * console. * * 404 means the backend simply has no data for the query — also non-fatal, let * the component render an empty state. * * Anything else (network errors, 5xx, validation) is a real problem and should * bubble up to the error boundary. */ function shouldBubbleToBoundary(error: unknown): boolean { if (error instanceof ApiError) { return error.status !== 401 && error.status !== 403 && error.status !== 404; } return true; } /** * Retry only when it's likely transient (network / 5xx). Don't retry 4xx auth * or validation failures — they won't fix themselves. */ function shouldRetry(failureCount: number, error: unknown): boolean { if (error instanceof ApiError) { if (error.status >= 400 && error.status < 500) return false; } return failureCount < 3; } function makeQueryClient() { return new QueryClient({ defaultOptions: { queries: { staleTime: 60 * 1000, gcTime: 5 * 60 * 1000, retry: shouldRetry, retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), refetchOnWindowFocus: false, throwOnError: shouldBubbleToBoundary, }, mutations: { retry: 1, }, }, }); } let browserQueryClient: QueryClient | undefined; export function getQueryClient() { if (typeof window === 'undefined') { return makeQueryClient(); } if (!browserQueryClient) { browserQueryClient = makeQueryClient(); } return browserQueryClient; }