Skip to Content

createGetQueryClient

createGetQueryClient is an experimental feature, so this interface may change.

A utility function to manage QueryClient instances in a server-safe manner.

  • On the server: Always creates a new QueryClient instance for each request
  • In the browser: Returns a singleton QueryClient instance, creating one if it doesn’t exist

This pattern is essential for proper React Query usage with SSR frameworks like Next.js, as it prevents sharing QueryClient state between requests on the server while maintaining a single instance in the browser to preserve cache across re-renders.

Sharing a QueryClient between requests on the server can lead to serious security vulnerabilities. getQueryClient creates a new QueryClient instance for each request to prevent data leakage between users and exposure of sensitive information. See the Server-Side Behavior section below for details.

Basic Usage

First, create a get-query-client.ts file in your project:

// get-query-client.ts import { createGetQueryClient } from '@suspensive/react-query' export const { getQueryClient } = createGetQueryClient()

Then, import and use it in your code:

// app/providers.tsx 'use client' import { QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { getQueryClient } from '@/app/get-query-client' import type * as React from 'react' export default function Providers({ children }: { children: React.ReactNode }) { const queryClient = getQueryClient() return ( <QueryClientProvider client={queryClient}> {children} <ReactQueryDevtools /> </QueryClientProvider> ) }

Configuration

You can pass a QueryClientConfig to createGetQueryClient to customize the QueryClient instance:

// get-query-client.ts import { createGetQueryClient } from '@suspensive/react-query' export const { getQueryClient } = createGetQueryClient({ defaultOptions: { queries: { staleTime: 5000, retry: 3, }, }, })
// app/providers.tsx 'use client' import { QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { getQueryClient } from '@/app/get-query-client' import type * as React from 'react' export default function Providers({ children }: { children: React.ReactNode }) { const queryClient = getQueryClient() return ( <QueryClientProvider client={queryClient}> {children} <ReactQueryDevtools /> </QueryClientProvider> ) }

Browser Environment Behavior

In the browser environment, the config provided when calling createGetQueryClient is used to create the QueryClient instance. The getQueryClient function does not accept any arguments and will always return the same singleton instance. This is intentional to maintain a stable singleton across re-renders.

Server-Side Behavior

Preventing Security Issues (Highest Priority)

Sharing a QueryClient between requests on the server can lead to serious security vulnerabilities:

  • Data leakage between users: One user’s data may be exposed to another user
  • Sensitive information exposure: Authentication tokens, personal information, etc. may be included in another user’s request
  • Incorrect cache state: Cached data from a previous request may be incorrectly returned to the next request

getQueryClient creates a new QueryClient instance for each request to completely prevent these security issues.

💡 Related documentation: For more information about why QueryClient should not be shared between requests on the server, see the TanStack Query SSR Guide - Initial Setup  section.

Setting Cache Removal Time to Prevent OOM

On the server, getQueryClient sets the cache removal time to Infinity to prevent memory leaks and OOM (Out of Memory) errors. This is an additional safety measure to address memory issues that can occur when servers handle many concurrent requests.

gcTime (v5) or cacheTime (v4) represents the time until TanStack Query removes query data from the QueryClient’s cache. By setting this to Infinity on the server:

  • Data is not automatically removed from the cache, keeping it available until the request completes
  • The isValidTimeout check in TanStack Query prevents setTimeout scheduling in scheduleGc, completely eliminating the performance cost that would occur with many concurrent requests

This behavior is automatic and cannot be overridden - even if you provide a different cache removal time value in your config, it will be set to Infinity on the server. (In v4, the cacheTime option is used; in v5, the gcTime option is used. See the Version Differences section below for details.)

💡 Related documentation: For more information about high memory consumption on server and why gcTime defaults to Infinity on the server, see the TanStack Query SSR Guide - High memory consumption on server  section.

// get-query-client.ts import { createGetQueryClient } from '@suspensive/react-query' // Config is passed to createGetQueryClient, not getQueryClient export const { getQueryClient } = createGetQueryClient({ defaultOptions: { queries: { // On server, cache removal time is set to Infinity // to keep cache alive until the request completes and prevent setTimeout scheduling gcTime: 5000, // v5: This will be overridden to Infinity on server // In v4, cacheTime is used and also overridden to Infinity }, }, }) // On server: Cache removal time is always Infinity (OOM prevention) // On browser: Uses the provided value (or default)

New Instance Per Request

On the server, getQueryClient creates a new QueryClient instance for each call. This ensures that:

  • Each request has isolated cache state
  • No data leaks between different user requests
  • Proper cleanup when the request completes
// get-query-client.ts import { createGetQueryClient } from '@suspensive/react-query' export const { getQueryClient } = createGetQueryClient()
// Server environment const queryClient1 = getQueryClient() const queryClient2 = getQueryClient() // queryClient1 !== queryClient2 (different instances)

Browser-Side Behavior

Singleton Pattern

In the browser, getQueryClient returns the same QueryClient instance across multiple calls. This ensures:

  • Cache is preserved across re-renders
  • Consistent state management
  • Optimal performance
// get-query-client.ts import { createGetQueryClient } from '@suspensive/react-query' export const { getQueryClient } = createGetQueryClient()
// Browser environment const queryClient1 = getQueryClient() const queryClient2 = getQueryClient() // queryClient1 === queryClient2 (same instance)

Use Cases

Next.js App Router

// get-query-client.ts import { createGetQueryClient } from '@suspensive/react-query' export const { getQueryClient } = createGetQueryClient()
// app/providers.tsx 'use client' import { QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { getQueryClient } from '@/app/get-query-client' import type * as React from 'react' export default function Providers({ children }: { children: React.ReactNode }) { const queryClient = getQueryClient() return ( <QueryClientProvider client={queryClient}> {children} <ReactQueryDevtools /> </QueryClientProvider> ) }
// app/layout.tsx import { Providers } from './providers' export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <Providers>{children}</Providers> </body> </html> ) }

With Custom Configuration

// get-query-client.ts import { createGetQueryClient } from '@suspensive/react-query' export const { getQueryClient } = createGetQueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5, // 5 minutes gcTime: 1000 * 60 * 10, // 10 minutes (v5) // cacheTime: 1000 * 60 * 10, // 10 minutes (v4) retry: 2, refetchOnWindowFocus: false, }, mutations: { retry: 1, }, }, })
// app/providers.tsx 'use client' import { QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { getQueryClient } from '@/app/get-query-client' import type * as React from 'react' export default function Providers({ children }: { children: React.ReactNode }) { const queryClient = getQueryClient() return ( <QueryClientProvider client={queryClient}> {children} <ReactQueryDevtools /> </QueryClientProvider> ) }

Version Differences

@tanstack/react-query v4

In v4, use the createGetQueryClient function, and the cache time option is called cacheTime:

// get-query-client.ts import { createGetQueryClient } from '@suspensive/react-query' export const { getQueryClient } = createGetQueryClient()
// get-query-client.ts import { createGetQueryClient } from '@suspensive/react-query' // Server: cacheTime is automatically set to Infinity export const { getQueryClient } = createGetQueryClient({ defaultOptions: { queries: { cacheTime: 5000, // Overridden to Infinity on server }, }, })
// app/providers.tsx 'use client' import { QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { getQueryClient } from '@/app/get-query-client' import type * as React from 'react' export default function Providers({ children }: { children: React.ReactNode }) { const queryClient = getQueryClient() return ( <QueryClientProvider client={queryClient}> {children} <ReactQueryDevtools /> </QueryClientProvider> ) }

@tanstack/react-query v5

In v5, use the createGetQueryClient function, and the cache time option is called gcTime (garbage collection time):

// get-query-client.ts import { createGetQueryClient } from '@suspensive/react-query' export const { getQueryClient } = createGetQueryClient()
// get-query-client.ts import { createGetQueryClient } from '@suspensive/react-query' // Server: gcTime is automatically set to Infinity export const { getQueryClient } = createGetQueryClient({ defaultOptions: { queries: { gcTime: 5000, // Overridden to Infinity on server }, }, })
// app/providers.tsx 'use client' import { QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { getQueryClient } from '@/app/get-query-client' import type * as React from 'react' export default function Providers({ children }: { children: React.ReactNode }) { const queryClient = getQueryClient() return ( <QueryClientProvider client={queryClient}> {children} <ReactQueryDevtools /> </QueryClientProvider> ) }

Important Notes

  • Server safety: Always use getQueryClient instead of creating new QueryClient() directly in SSR environments to ensure proper request isolation and OOM prevention.

Never use QueryClient as a global variable

Even if you use getQueryClient, storing it in a global variable like export const queryClient = getQueryClient() in App Router and importing it elsewhere will cause the same security risks. The QueryClient instance will be shared between requests on the server, leading to data leakage between users. Always call getQueryClient() directly where you need it, rather than storing it in a global variable.

  • Browser singleton: The browser instance is a singleton. Configure your QueryClient when calling createGetQueryClient, as getQueryClient does not accept any arguments.

  • Automatic OOM prevention: The cache removal time override to Infinity on the server is automatic and cannot be disabled. This is a safety feature to prevent memory issues. (In v4, the cacheTime option is used; in v5, the gcTime option is used.)

Last updated on