TanStack / query

🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.
https://tanstack.com/query
MIT License
42.67k stars 2.92k forks source link

Next js 15 with dynamicIO: true returned server error #8277

Closed yudistiraashadi closed 2 weeks ago

yudistiraashadi commented 2 weeks ago

Describe the bug

Running the "React Example: Nextjs App Prefetching" from the docs with dynamicIO: true setting on next.config.ts return this error shown below. The code is available here.

Image

Your minimal, reproducible example

https://github.com/yudistiraashadi/next15-tanstack-query-canary

Steps to reproduce

  1. Install next.js 15 canary npx create-next-app@canary
  2. Install tanstack query and devtools pnpm add @tanstack/react-query and pnpm add -D @tanstack/react-query-devtools
  3. Copy all the code from the "React Example: Nextjs App Prefetching" here, to replace all the code inside src folders on the nextjs app folder (or /src folder, I'm using src folder btw)
  4. Run dev server pnpm run dev and open localhost:3000

Expected behavior

No error

How often does this bug happen?

Every time

Screenshots or Videos

https://github.com/user-attachments/assets/54bbb464-f4a2-4030-b307-04df16852898

Platform

Tanstack Query adapter

None

TanStack Query version

5.59.20

TypeScript version

5.6.3

Additional context

No response

TkDodo commented 2 weeks ago

Not sure if I discussed this with you, but it came up on Discord as well. The reason is likely that we initiate our mutationIds with Date.now:

https://github.com/TanStack/query/blob/24f1d45cbd3a252e2c8bb3471591502a2418b6ad/packages/query-core/src/mutationCache.ts#L91

It doesn’t have to be Date.now, but it needs to be a unique id that we can increment and that doesn’t overlap with ids when they become restored them from localStorage.

If you have an idea what that could be, please file a PR.

TkDodo commented 2 weeks ago

Actually, we only serialize the scope, which defaults to String(mutation.mutationId):

https://github.com/TanStack/query/blob/24f1d45cbd3a252e2c8bb3471591502a2418b6ad/packages/query-core/src/mutationCache.ts#L205-L207

So we can also initialize mutations with 0 (like it was before we had that feature) IF we find a way to generate a unique string as a scope for mutations. We sadly can’t use crypto.randomUUID because it’s not available in all the browsers we support.

yudistiraashadi commented 2 weeks ago

(edit) DISCLAIMER: THIS DOESN'T WORK !!

Honestly, I can't think of any way to consistently generate unique strings for older browsers. Currently, I tried to use await connection() based on the Next.js docs here. Basically creating 2 getQueryClient(), one for server and one for browser. I'm not sure what the await connection() does or the performance implication of awaiting before new QueryClient(queryClientConfig) but at least it doesn't throw any error now.

import {
  QueryClient,
  defaultShouldDehydrateQuery,
  type QueryClientConfig,
} from "@tanstack/react-query";
import { connection } from "next/server";

const queryClientConfig: QueryClientConfig = {
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000,
    },
    dehydrate: {
      // include pending queries in dehydration
      shouldDehydrateQuery: (query) =>
        defaultShouldDehydrateQuery(query) || query.state.status === "pending",
    },
  },
};

let browserQueryClient: QueryClient | undefined = undefined;

export async function getServerQueryClient() {
  await connection();
  return new QueryClient(queryClientConfig);
}

export function getBrowserQueryClient() {
  // Browser: make a new query client if we don't already have one
  // This is very important, so we don't re-make a new client if React
  // suspends during the initial render. This may not be needed if we
  // have a suspense boundary BELOW the creation of the query client
  if (!browserQueryClient) {
    browserQueryClient = new QueryClient(queryClientConfig);
  }

  return browserQueryClient;
}
yudistiraashadi commented 1 week ago

I'm writing this as a note for future developers. I ended up patching @tanstack/query-core to use crypto.randomUUID() according to @TkDodo comments https://github.com/TanStack/query/issues/8277#issuecomment-2468285922 and https://github.com/TanStack/query/issues/8277#issuecomment-2468295920. If anyone has a better solution please comment here.

firatciftci commented 6 days ago

Apologies for commenting on a closed issue, but do we have a final (and official) verdict on what to do to be able to use TanStack Query with Next.js 15 Canary? I understand that it is an experimental build and so the package maintainers should not spend their time and effort to support something that is not finalized and stable, but if there is any way to utilize TanStack Query with the dynamicIO and PPR features and test it to be ready for an upgrade once they land in main, I would love to learn how.

ali-idrizi commented 6 days ago

crypto.randomUUID() also throws the same error according to https://nextjs.org/docs/messages/next-prerender-crypto.

Perhaps performance.timeOrigin + performance.now() is a good option, just like the error suggests? It's generally equal to Date.now() and it seems to have pretty good browser support.

TkDodo commented 6 days ago

Didn’t know about performance.timeOrigin. The suggestion to use performance.timeOrigin + performance.now() seems good 👍 . Would you want to create a PR for this @ali-idrizi ?

ali-idrizi commented 6 days ago

Absolutely! I just created the PR that fixes this.

jk2908 commented 3 hours ago

Before the fix is released, adding a Suspense boundary w/ null fallback directly above your Providers works.