diff --git a/integrations/react-next-15/app/client-component.tsx b/integrations/react-next-15/app/client-component.tsx
index 29dd7b33c9..8a6525310f 100644
--- a/integrations/react-next-15/app/client-component.tsx
+++ b/integrations/react-next-15/app/client-component.tsx
@@ -1,11 +1,11 @@
'use client'
import React from 'react'
-import { useQuery } from '@tanstack/react-query'
+import { useQuery, useSuspenseQuery } from '@tanstack/react-query'
import { Temporal } from '@js-temporal/polyfill'
export function ClientComponent() {
- const query = useQuery({
+ const query = useSuspenseQuery({
queryKey: ['data'],
queryFn: async () => {
await new Promise((r) => setTimeout(r, 1000))
@@ -16,13 +16,13 @@ export function ClientComponent() {
},
})
- if (query.isPending) {
- return
Loading...
- }
+ // if (query.isPending) {
+ // return Loading...
+ // }
- if (query.isError) {
- return An error has occurred!
- }
+ // if (query.isError) {
+ // return An error has occurred!
+ // }
return (
diff --git a/integrations/react-next-15/app/make-query-client.ts b/integrations/react-next-15/app/make-query-client.ts
index 3d0ff40cb8..d970cc22b3 100644
--- a/integrations/react-next-15/app/make-query-client.ts
+++ b/integrations/react-next-15/app/make-query-client.ts
@@ -26,6 +26,7 @@ export function makeQueryClient() {
},
queries: {
staleTime: 60 * 1000,
+ warnOnServerFetches: true,
},
dehydrate: {
serializeData: tson.serialize,
diff --git a/integrations/react-next-15/app/page.tsx b/integrations/react-next-15/app/page.tsx
index 2382ab540f..df3e51e056 100644
--- a/integrations/react-next-15/app/page.tsx
+++ b/integrations/react-next-15/app/page.tsx
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, { Suspense } from 'react'
import { HydrationBoundary, dehydrate } from '@tanstack/react-query'
import { Temporal } from '@js-temporal/polyfill'
import { ClientComponent } from './client-component'
@@ -9,21 +9,23 @@ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
export default async function Home() {
const queryClient = makeQueryClient()
- void queryClient.prefetchQuery({
- queryKey: ['data'],
- queryFn: async () => {
- await sleep(2000)
- return {
- text: 'data from server',
- date: Temporal.PlainDate.from('2024-01-01'),
- }
- },
- })
+ // void queryClient.prefetchQuery({
+ // queryKey: ['data'],
+ // queryFn: async () => {
+ // await sleep(2000)
+ // return {
+ // text: 'data from server',
+ // date: Temporal.PlainDate.from('2024-01-01'),
+ // }
+ // },
+ // })
return (
-
+ Loading...
}>
+
+
)
diff --git a/packages/query-core/src/types.ts b/packages/query-core/src/types.ts
index b13e52d16c..bb23edce86 100644
--- a/packages/query-core/src/types.ts
+++ b/packages/query-core/src/types.ts
@@ -223,6 +223,12 @@ export interface QueryOptions<
* Maximum number of pages to store in the data of an infinite query.
*/
maxPages?: number
+ /**
+ * If set to `true`, the query will warn if it is fetched on the server.
+ * Use this if you intend to only consume prefetched queries.
+ * Defaults to `false`.
+ */
+ warnOnServerFetches?: boolean
}
export interface InitialPageParam {
diff --git a/packages/react-query/src/__tests__/useSuspenseQuery.test.tsx b/packages/react-query/src/__tests__/useSuspenseQuery.test.tsx
index 003655ec1d..f235552170 100644
--- a/packages/react-query/src/__tests__/useSuspenseQuery.test.tsx
+++ b/packages/react-query/src/__tests__/useSuspenseQuery.test.tsx
@@ -903,4 +903,100 @@ describe('useSuspenseQuery', () => {
)
consoleErrorSpy.mockRestore()
})
+
+ describe.only("warnOnServerFetches should warn when query isn't prefetched", () => {
+ it('global default', async () => {
+ const queryClient = createQueryClient({
+ defaultOptions: {
+ queries: {
+ warnOnServerFetches: true,
+ },
+ },
+ })
+
+ const consoleWarnSpy = vi
+ .spyOn(console, 'warn')
+ .mockImplementation(() => {})
+
+ function App() {
+ useSuspenseQuery({
+ queryKey: queryKey(),
+ queryFn: () => Promise.resolve('data1'),
+ })
+
+ return null
+ }
+
+ renderWithClient(queryClient, )
+
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
+ 'A new query suspended on the server without any cache entry.',
+ )
+ })
+
+ it('local override', async () => {
+ const queryClient = createQueryClient({
+ defaultOptions: {
+ queries: {
+ warnOnServerFetches: true,
+ },
+ },
+ })
+
+ const consoleWarnSpy = vi
+ .spyOn(console, 'warn')
+ .mockImplementation(() => {})
+
+ function App() {
+ useSuspenseQuery({
+ queryKey: queryKey(),
+ queryFn: () => Promise.resolve('data1'),
+ warnOnServerFetches: false,
+ })
+
+ return null
+ }
+
+ renderWithClient(queryClient, )
+
+ expect(consoleWarnSpy).not.toHaveBeenCalled()
+ })
+
+ it('mix of both', async () => {
+ const queryClient = createQueryClient({
+ defaultOptions: {
+ queries: {
+ warnOnServerFetches: true,
+ },
+ },
+ })
+
+ const consoleWarnSpy = vi
+ .spyOn(console, 'warn')
+ .mockImplementation(() => {})
+
+ function Component(props: { warnOnServerFetches?: boolean }) {
+ useSuspenseQuery({
+ queryKey: queryKey(),
+ queryFn: () => Promise.resolve('data'),
+ warnOnServerFetches: props.warnOnServerFetches,
+ })
+
+ return null
+ }
+
+ function App() {
+ return (
+ <>
+
+
+ >
+ )
+ }
+
+ renderWithClient(queryClient, )
+
+ expect(consoleWarnSpy).not.toHaveBeenCalledTimes(1)
+ })
+ })
})
diff --git a/packages/react-query/src/useBaseQuery.ts b/packages/react-query/src/useBaseQuery.ts
index bcbf700ef7..dbe2c22771 100644
--- a/packages/react-query/src/useBaseQuery.ts
+++ b/packages/react-query/src/useBaseQuery.ts
@@ -111,6 +111,11 @@ export function useBaseQuery<
// Handle suspense
if (shouldSuspend(defaultedOptions, result)) {
+ if (defaultedOptions.warnOnServerFetches && isNewCacheEntry) {
+ console.warn(
+ 'A new query suspended on the server without any cache entry.',
+ )
+ }
throw fetchOptimistic(defaultedOptions, observer, errorResetBoundary)
}