Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

useAsyncData and useFetch run twice if page uses layout #13369

Closed
lukaszflorczak opened this issue Feb 10, 2022 · 49 comments · Fixed by nuxt/framework#3670
Closed

useAsyncData and useFetch run twice if page uses layout #13369

lukaszflorczak opened this issue Feb 10, 2022 · 49 comments · Fixed by nuxt/framework#3670

Comments

@lukaszflorczak
Copy link

lukaszflorczak commented Feb 10, 2022

Environment

------------------------------
- Operating System: `Darwin`
- Node Version:     `v17.4.0`
- Nuxt Version:     `3.0.0-27408103.b243891`
- Package Manager:  `npm@8.3.1`
- Bundler:          `Vite`
- User Config:      `-`
- Runtime Modules:  `-`
- Build Modules:    `-`
------------------------------

Reproduction

https://github.com/lukaszflorczak/nuxt3-demo/tree/test/fetch (branch test/fetch).

  1. Run the / page and check console logs
  2. Go to index.vue and set layout: false in definePageMeta()
  3. Refresh the page

Describe the bug

Today I noticed that useAsyncData and useFetch run requests twice – both server and client-side. I tested it in component and page.

During my investigation, I found, that the problem exists only if the page uses a layout.

Probably it's related to nuxt/framework#3011

@alancolant
Copy link

Same problem here

@Tahul
Copy link
Contributor

Tahul commented Feb 22, 2022

Hello!

We experience the same issue in @nuxt/content docs theme, to lighten the investigation I also built reproduction on StackBlitz.

You can see there that on first page load, the random integer will be refetched on client side and the data fetched server side won't be preserved.

https://stackblitz.com/edit/nuxt3-repro-utzcxx?file=pages%2F[...id].vue

@danielroe
Copy link
Member

This seems to be an upstream issue: see vuejs/router#626 (comment)

@fazulk
Copy link

fazulk commented Feb 23, 2022

@danielroe - I noticed this as well, So i removed layout and the issue was resolved. Although, once i introduced a second API call using useAsyncData the issue returned, but only for one of the API calls.

<script setup>
const { data } = await useAsyncData("count", () => $fetch("/api/counter"));

// this will run both serverside AND browser side
const { data: secondCounter } = await useAsyncData("secondCount", () => $fetch("/api/counter-2"));
</script>

@Baiyuetribe
Copy link

I did the same thing with the backend written in golang, using useFetch or useAsyncData to request api, both triggered twice.This problem needs to be solved urgently

@danielroe
Copy link
Member

@Baiyuetribe From what you've said, it's possible your browser is requesting a favicon along with the HTML request.

If you don't have a pages directory or a favicon, Nuxt will respond to both requests with a full HTML page, which means you will get two asyncData requests to your API.

The specific bug here seems to be upstream in Vue, triggered by having nested Suspense components, either because of a layout wrapping a page or a page wrapping a child page (on a nested route)...

@Baiyuetribe
Copy link

@danielroe I'm not talking about the icon problem, I use golang to develop the back-end api interface and then use nuxt3 to call the api to achieve server-side rendering. Even in the minimal demo, the request is triggered twice.In the default app.vue

<template>
  <div>
    <h3>{{ data }}</h3>
  </div>
</template>

<script setup>
const { data } = await useFetch('http://192.168.1.3:363/api/home', { server: true }) 
</script>

I think the first time the usefetch may have worked, the second time it may have been triggered by entry-xxx.mjs from the client.
image

@danielroe
Copy link
Member

@Baiyuetribe Would you provide a reproduction? I'm still thinking the issue in your case is the extra favicon request.

@alancolant
Copy link

@danielroe I provide a reproduction here:
Repro

@Baiyuetribe
Copy link

Baiyuetribe commented Feb 26, 2022

@danielroe sandbox https://stackblitz.com/edit/github-5qtamp?file=app.vue
Just run yarn build && yarn start or yarn dev as well
image

You can see that after one server-side request, the client will request it again. So it causes second requests.

@danielroe
Copy link
Member

@Baiyuetribe Your issue is unrelated to this particular thread.

The cause is your browser requesting favicon.ico automatically. Because this file doesn't exist, and you are handling all requests with app.vue (rather than using a router) this means you will receive two requests to your Nuxt server for every single reload in your browser.

As a consequence, you will receive two requests to your API. That can easily make you think that it's running twice, even though it is only running once per render. It is easily solved by adding a ~/public/favicon.ico file. (Longer term, we are likely to add a default 1px transparent ico to avoid this issue, but that is pending #13360.)

CleanShot 2022-02-26 at 13 13 25@2x

@Baiyuetribe
Copy link

@danielroe Thank you very much. It works fine after adding favicon.ico. Highly recommend adding it to the template, I almost gave up because of this problem.

@Baiyuetribe
Copy link

image

After fixing the icon issue,When there are layouts, it does lead to a second request from the client, and the problem can lead to repeated rendering of the page
@danielroe sandbox https://stackblitz.com/edit/github-5qtamp-u2ftnu?file=pages/index.vue

@danielroe
Copy link
Member

@Baiyuetribe Please see #13369.

@Baiyuetribe
Copy link

@danielroe When useFetch parameter server: true, can the client block the request?
Or is there any temporary solution? Not solving the problem will lead to duplicate requests and rendering of page data, and the worst will lead to secondary requests with errors.

@Baiyuetribe
Copy link

Baiyuetribe commented Feb 27, 2022

Temporarily, I found that after deleting the layouts file, simply using pages is normal.
Hope to modify the duplicate rendering problem caused by layouts soon

@Baiyuetribe
Copy link

@danielroe sandbox https://stackblitz.com/edit/github-5qtamp-82ca6q?file=app.vue
New solution, delete layouts and put the contents in app.vue and everything is back to normal. It seems the problem can only be with layouts.
image

@danielroe
Copy link
Member

danielroe commented Feb 27, 2022

@Baiyuetribe The problem does not seem to be with Nuxt but with Suspense, which is why I linked my comment above, and why this is labelled upstream.

This issue exists, at the moment, to track the resolution of the issue within vuejs/core or vue-router.

If you spot an issue with the way layouts or pages are implemented in Nuxt, of course I would be very glad to hear that.

@serhez
Copy link

serhez commented Apr 1, 2022

@codelegant I've tried the fetching with if (process.server) as well as with Vue's onServerPrefetch() hook, but doing this introduces a lot of hydration mismatches for me, since you need to copy the async data to an existing ref. There is most certainly a good way to do it, possibly with a watch or something, but I haven't found it 😞

@serhez
Copy link

serhez commented Apr 1, 2022

@danielroe thanks for the info, I am not on the latest version. I might as well wait until the RC is released (7th of this month? 🙏🏻 ) to upgrade. I'll probably forget to post here when I upgrade if the issue is fixed, but then you can assume it is 😛

@codelegant
Copy link

@codelegant I've tried the fetching with if (process.server) as well as with Vue's onServerPrefetch() hook, but doing this introduces a lot of hydration mismatches for me, since you need to copy the async data to an existing ref. There is most certainly a good way to do it, possibly with a watch or something, but I haven't found it 😞

The async data no need fetching in server.

@mjrobinson86
Copy link

mjrobinson86 commented Apr 1, 2022

@danielroe this last fix solved my problem perfectly, so I think the core issue is resolved at least as far as I can tell.

@NidMo
Copy link

NidMo commented Jun 17, 2022

@Baiyuetribe The problem does not seem to be with Nuxt but with Suspense, which is why I linked my comment above, and why this is labelled upstream.

This issue exists, at the moment, to track the resolution of the issue within vuejs/core or vue-router.

If you spot an issue with the way layouts or pages are implemented in Nuxt, of course I would be very glad to hear that.

if page uses layout, child page will setup twice

@nathanchase
Copy link
Contributor

I'm on "nuxt": "3.0.0-rc.5", and I'm still seeing this same behavior. All useFetch (or $fetch) calls occur twice - once SSR, and again on client. It doesn't matter if I am using a layout or not.

@danielroe
Copy link
Member

@nathanchase would you create a new issue with a reproduction? 🙏

@nathanchase
Copy link
Contributor

@nathanchase would you create a new issue with a reproduction? 🙏

It appears to be pinia at fault. If I delete pinia from my modules in nuxt.config, useFetch/$fetch no longer loads data twice.

I'm using "@pinia/nuxt": "^0.3.0", and "pinia": "^2.0.16".

Will try to create a barebones repro.

@nathanchase
Copy link
Contributor

@danielroe False alarm. It was a matter of data being pushed into an array errantly inside a for loop inside a store action.

@devCrossNet
Copy link

devCrossNet commented Jan 3, 2023

Hey, I just came across this thread because I faced the same issue. When using a Pinia action within useAsyncData the action gets invoked twice (1st server, 2nd client).

I boiled it down to this issue:
Pinia actions don't return any data which leads to an empty cache (you can see this in this line: https://github.com/nuxt/framework/blob/main/packages/nuxt/src/app/composables/asyncData.ts#L113).

I wrote a wrapper composable to have a workaround that looks like this:

// Use this composable to prefill a store with an action
// Hack to tell nuxt that this function was executed
export const usePrefillStoreAction = async (action: () => Promise<unknown>) => {
  await useAsyncData(async () => {
    await action();
    // TODO: create issue that Pinia somehow needs to return a value from the action?
    return Promise.resolve('true');
  });
};

Not sure if I need to file an issue on the Nuxt repo or Pinia repo.

Let me know what you think.

cc: @danielroe

@danielroe
Copy link
Member

In this case you might even find it better to use the vue onServerPrefetch hook rather than using a side effect of useAsyncData, which we would generally recommend against.

@devCrossNet
Copy link

@danielroe my use case is pretty simple: I want to invoke a pinia action to get page-specific data whenever a user navigates to that page, either by visiting it directly or on client-side navigation. As far as the docs say and the name implies onServerPrefetch would only cover the case when the user directly visits the page and not on client-side navigation. Or am I getting this wrong?

In Nuxt 2 asyncData was sufficient for this basic use case.

@danielroe
Copy link
Member

Yes, that's right.

@devCrossNet
Copy link

@danielroe what would be your recommended solution then? Not using Pinia?

@nathanchase
Copy link
Contributor

@danielroe what would be your recommended solution then? Not using Pinia?

The way I've done it is like so:

const { data: user } = await useAsyncData(username, async () => {
  await usersStore.getUser(username); // <- this is calling the getUser action from the usersStore
  return usersStore.user; // <- this returns the actual state value from the store once the above promise resolves
});

@devCrossNet
Copy link

@nathanchase good to know that this works. I will have a look and think about the pros and cons between your solution and mine. Thanks a lot 👍

@danielroe danielroe added the 3.x label Jan 19, 2023
@danielroe danielroe transferred this issue from nuxt/framework Jan 19, 2023
@Nicolas-Nmedia
Copy link

Nicolas-Nmedia commented Jan 27, 2023

So, FWIW... I've stumbled upon this behavior while working on a simple app to test out Nuxt 3 and I've noticed that it exists even without a template going on. I tested a couple of solutions others have mentionned but to no avail so I tried something; used a reactive value instead of constant to generate my API url and lo and behold, it ended up working. Sadly, I'm no nuxt/Vue wizard, but I thought I'd share this with you guys as it could maybe help you guys out figuring the root of this.

Here's a StackBlitz example, you'll see the second Pokemon flashing on the client side fetching.

PS: see, this is what happens when you have a 4 year run old in your house, all your test cases end up being about Pokemons ;x

@MrSunshyne
Copy link

MrSunshyne commented Apr 2, 2023

@Nicolas-Nmedia @milelazar @danielroe
I've been struggling with this issue since a couple of months now. I run a CMS on my local machine, and I wanted to use Nuxt SSG to build the site and ship that to vercel/netlify. The fact that the api call happens on the client side is not only an inconvenience for me, but its completely breaking, as in my case the CMS is not accessible online after deployment.

I've tried countless things trying to debug this but just can't seem to figure it out. So far what I've noticed is that:

  1. In two routes I have an api call to /api/posts through a simple useFetch which returns a bunch posts. I want to list posts on a page. After ssg build, npm run generate

const { data: posts } = await useFetch("/api/posts");

Route 1: /

  • Client side api call to /api/posts happens. Wrong behaviour

Route 2: /blog

  • Client side api call to /api/posts doesn't happened on /blog. Correct behaviour.

Both pages contain the exact same code.

I've tried removing layout and useing app.vue to avoid the Suspense error.

You can see the behavior on this link. Inspect and see network tab. You'll notice the "Recent blog posts" section appear and then disappear because a client side call happened and didn't hit any endpoints because this is SSG.

@danielroe
Copy link
Member

@MrSunshyne would you create a new issue with a reproduction? That should not be happening.

@MrSunshyne
Copy link

@danielroe I managed to narrow it down to this. It appears the problem is not nuxt3 itself but looks like something could be related to this issue #19887

@milelazar
Copy link

@MrSunshyne have you tried useNuxtData? Maybe you can build a caching logic. Fetch some data with useFetch and then useNuxtData pulls that cached data without fetching again. This solved my problem with refetching.

Take a look at this: #19912 (comment)

@treb00
Copy link

treb00 commented Apr 7, 2023

In my project useFetch only runs twice when it gets 500 response. Does this maybe relates to this issue? Also using layout and running with nuxt 3.3.3

@NeoKms
Copy link

NeoKms commented May 2, 2023

@danielroeкакое бы вы рекомендовали решение тогда? Не используете Пинию?

То, как я это сделал, выглядит так:

const { data: user } = await useAsyncData(username, async () => {
  await usersStore.getUser(username); // <- this is calling the getUser action from the usersStore
  return usersStore.user; // <- this returns the actual state value from the store once the above promise resolves
});

This really worked for me. I still don't understand why, but thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.