Files
MOPC-Portal/src/app/providers.tsx

90 lines
3.1 KiB
TypeScript
Raw Normal View History

'use client'
import { useState } from 'react'
import { SessionProvider } from 'next-auth/react'
import { ThemeProvider } from 'next-themes'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { httpBatchLink } from '@trpc/client'
import superjson from 'superjson'
import { trpc } from '@/lib/trpc/client'
function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
refetchOnWindowFocus: false,
retry: (failureCount, error) => {
// Retry up to 3 times on server errors (503 cold-start, etc.)
if (failureCount >= 3) return false
const msg = (error as Error)?.message ?? ''
// Retry on JSON parse errors (HTML 503 from nginx) and server errors
if (msg.includes('is not valid JSON') || msg.includes('Unexpected token')) return true
if (msg.includes('500') || msg.includes('502') || msg.includes('503')) return true
return failureCount < 2
},
retryDelay: (attemptIndex) => Math.min(2000 * (attemptIndex + 1), 8000),
},
},
})
}
let browserQueryClient: QueryClient | undefined = undefined
function getQueryClient() {
if (typeof window === 'undefined') {
// Server: always make a new query client
return makeQueryClient()
} else {
// Browser: make a new query client if we don't already have one
if (!browserQueryClient) browserQueryClient = makeQueryClient()
return browserQueryClient
}
}
function getBaseUrl() {
if (typeof window !== 'undefined') return ''
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`
return `http://localhost:${process.env.PORT ?? 3000}`
}
export function Providers({ children }: { children: React.ReactNode }) {
const queryClient = getQueryClient()
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
transformer: superjson,
async fetch(url, options) {
const res = await globalThis.fetch(url, options)
// Detect nginx 503 / HTML error pages before tRPC tries to JSON.parse
if (!res.ok) {
const ct = res.headers.get('content-type') ?? ''
if (ct.includes('text/html') || !ct.includes('json')) {
throw new Error(
res.status >= 500
? 'Server is starting up — please wait a moment and try again.'
: `Server error (${res.status})`
)
}
}
return res
},
}),
],
})
)
return (
<ThemeProvider attribute="class" defaultTheme="light" enableSystem={false}>
<SessionProvider>
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</trpc.Provider>
</SessionProvider>
</ThemeProvider>
)
}