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
41.9k stars 2.85k forks source link

[vue-query]: Different queryCache behavior on SSR for Nuxt3 and Nuxt2 #7877

Closed manuelarling closed 4 weeks ago

manuelarling commented 1 month ago

Describe the bug

The behavior for queryClient using server-side rendering is different and works incorrectly in Nuxt2/Vue2. On Nuxt3/Vue3 the expected behavior occurs.

In Nuxt 3, the queryCache is likely reset for each request. This is not the case in Nuxt 2.

This leads to unexpected behavior in the following example use case. When user 1 requests a page, the list of to-dos belonging to user 1 is fetched and displayed. User 2 then accesses the site, but when the page is loaded on the client, the list of to-dos belonging to User 1 is displayed, and the to-dos belonging to User 2 are not displayed until after the refetch on the client page. This creates a privacy issue.

This use case is shown as an example in the two repos. To simulate two different users, the url param is set differently. This is to symbolize the different responses from a server.

Your minimal, reproducible example

Nuxt 2: https://codesandbox.io/p/devbox/gracious-hermann-lkc3rq?workspaceId=684c7eda-e5ab-41ca-9242-874b5f17c5ae

Nuxt 3: https://codesandbox.io/p/devbox/proud-wood-h3syhd?workspaceId=684c7eda-e5ab-41ca-9242-874b5f17c5ae

Steps to reproduce

  1. Get the nuxt app to running
  2. Open the website in one tab with the following url: /abc
  3. Open the website in a second tab with the following url: /123
  4. Reload the first tab
  5. Reload the second tab

Do this for both repos and you will see that the behavior is different and you will see that the logs in the browser devtools and the terminal are different.

Expected behavior

The Behaviour on Nuxt2/Vue2 should be the same as on Nuxt3/Vue3.

How often does this bug happen?

Every time

Screenshots or Videos

Nuxt 2:

https://github.com/user-attachments/assets/574214fb-b50e-4af0-84d2-b5a2cfe15588

Nuxt 3:

https://github.com/user-attachments/assets/b5715d5e-4fa0-4023-a646-4331a83cbc8a

Platform

Tanstack Query adapter

vue-query

TanStack Query version

v5.51.21

TypeScript version

No response

Additional context

No response

DamianOsipiuk commented 1 month ago

You are right.

This is due to the fact that in Vue2, Vue instance is shared across requests. Clearing it between requests is not enough, cause there might be couple requests in-flight at the same time.

I believe you might achieve proper per-request separation with slight modification of the plugin and component code.

Sandbox with the fix

Plugin code:

import Vue from "vue";
import { VueQueryPlugin, QueryClient, hydrate } from "@tanstack/vue-query";

export default (context) => {
  // Modify your Vue Query global settings here
  const queryClient = new QueryClient({
    defaultOptions: { queries: { staleTime: 5000 } },
  });

  if (process.server) {
    context.ssrContext.VueQuery = queryClient;
  }

  if (process.client) {
    Vue.use(VueQueryPlugin, { queryClient });

    if (context.nuxtState && context.nuxtState.vueQueryState) {
      console.log(
        "Hydrating state from server",
        context.nuxtState.vueQueryState
      );
      hydrate(queryClient, context.nuxtState.vueQueryState);
    }
  }
};

In component, move queryClient retrieval to the top and pass queryClient to each useQuery call as a second parameter:

const { ssrContext } = useContext();
const queryClient = (ssrContext != null && ssrContext.VueQuery) || useQueryClient()

const { data: data1, suspense } = useQuery({
  queryKey: ["todos"],
  queryFn: fetcher,
}, queryClient);
manuelarling commented 1 month ago

First of all, thank you for your reply that helped a lot.

I was missing the first information, and then I did some more research. I found out that the problem is also called cross-request state pollution. The difference in the two examples in the documentation is that the Nuxt 3 example uses the useState Composable. This fixes the problem by creating a separate state object for each request.

In any case, I would expect the two examples in the documentation to have the same behavior, or at least in the Nuxt 2 example a hint of the potential problem.

Yes, I am looking for a solution that implements a version for creating queryClient per request. Is there perhaps another suggestion, as unfortunately this one requires a lot of refactoring and also attention must be paid in the future.

DamianOsipiuk commented 4 weeks ago

Docs have been updated.

Difference in behavior is because in Vue2 Vue instance is shared between requests.

If you find a better way to simplify Nuxt2 setup, please open a PR.