storyblok / storyblok-nuxt

Storyblok Nuxt module
https://www.storyblok.com/tp/nuxt-js-multilanguage-website-tutorial
MIT License
277 stars 43 forks source link

Storyblok making requests clientside on full static site. #769

Closed mrillusion closed 6 months ago

mrillusion commented 8 months ago

Describe the issue you're facing

I have tried various methods to statically generate nuxt 3 pages from storyblok but every method I try results in fetch requests on route change (when visiting a route for the first time).

I have tried using the 'useStoryblokApi' method

<script setup>
const { slug } = useRoute().params;
const runtimeConfig = useRuntimeConfig();
const storyblokVersion = runtimeConfig.public.storyblokVersion;
const storyPath = slug && slug.length > 0 ? slug.join('/') : 'home';

const storyblokApi = useStoryblokApi();

  const { data } = await useAsyncData(
    storyPath,
    async () =>
      await storyblokApi.get(
        "cdn/stories/" + storyPath,
        {
          version: storyblokVersion,
        }
      )
  );

  const story = data.value.data.story;
</script>

<template>
  <div class="flex flex-col">
    <StoryblokComponent v-if="story" :blok="story.content" />
  </div>
</template>

I have also tried the 'useAsyncStoryblok' shorthand (see repoduction on stackblitz for example).

Neither work?

Reproduction

https://stackblitz.com/edit/nuxt-storyblok-fetch-issue?file=pages%2F%5B...slug%5D.vue

Steps to reproduce

In the gif below I have recorded an example showing the issue.

Current Behaviour - On first visit of a page via route change a fetch request is made when the page has been prerendered

Expected Behaviour - On any visit to a page via route change or otherwise no fetch request should be made.

CleanShot 2024-03-08 at 10 37 48

System Info

System:
    OS: macOS 14.3.1
    CPU: (11) arm64 Apple M3 Pro
    Memory: 4.83 GB / 36.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 21.6.2 - ~/.nvm/versions/node/v21.6.2/bin/node
    npm: 10.2.4 - ~/.nvm/versions/node/v21.6.2/bin/npm
    pnpm: 8.15.4 - ~/.nvm/versions/node/v21.6.2/bin/pnpm
  Browsers:
    Chrome: 122.0.6261.112
    Safari: 17.3.1

Used Package Manager

pnpm

Error logs (Optional)

No response

Validations

Youhan commented 8 months ago

I can also replicate this using Nuxt 3.10.3 with Nitro 2.9.1 and @storyblok/nuxt@6.0.4

Youhan commented 8 months ago

It looks like the issue is that the <StoryblokComponent is rendering a dynamic component and Nuxt needs an import for the dynamic component(docs).

I ended up with a workaround that seems working. Please try and let me know.

on the page content type component, or any component that is rendering a list of children component, instead of this:

<script setup lang="ts">
import type { CtPageStoryblok } from '~/component-types-sb'

defineProps<{ blok: CtPageStoryblok }>()
</script>

<template>
    <div v-editable="blok">
      <StoryblokComponent v-for="item in blok.body" :key="item._uid" :blok="item" :uuid="item._uid" />
    </div>
</template>

I used this:

<script setup lang="ts">
import type { CtPageStoryblok } from '~/component-types-sb'
import { FeaturedArticlesHero } from '#components'

defineProps<{ blok: CtPageStoryblok }>()

const knownAsyncDataComponents: Record<string, any> = {
  'featured-articles-hero': markRaw(FeaturedArticlesHero),
}

function getComponent(item: any) {
  const comp = knownAsyncDataComponents[item.component]
  return {
    ...item,
    component: comp || item.component,
  }
}
</script>

<template>
  <div v-editable="blok">
    <StoryblokComponent v-for="item in blok.body" :key="item._uid" :blok="getComponent(item)" />
  </div>
</template>

Here is what /component/FeaturedArticlesHero.vue looks like. Note that it doesn't matter if you fetch from StoryBlock API directly or use useAsyncStoryBlok or just a simple fetch to jsonplaceholder. The problem seems to be the dynamic components.

<script setup lang="ts">
import type { FeaturedArticlesHeroStoryblok } from '~/component-types-sb'
const props = defineProps<{
  blok: FeaturedArticlesHeroStoryblok
}>()
const { data } = await useAsyncData(
  `testing----${props.blok._uid}`,
  () => $fetch('https://jsonplaceholder.typicode.com/todos/1'),
)
</script>

<template>
  <div>
    Featured Articles Hero
    <pre>{{ data }}</pre>
  </div>
</template>
mrillusion commented 7 months ago

Hi @Youhan I attempted this workaround and I am not seeing any changes/difference.

I did update it somewhat to handle any component name rather than individually importing all components.

// Page.vue
<script setup>
const props = defineProps({
  blok: {
    type: Object,
    required: true,
  },
})

const knownAsyncDataComponents = markRaw(props.blok.component);

function getComponent(item) {
  const AsyncComponent = knownAsyncDataComponents[item.component]
  return {
    ...item,
    component: AsyncComponent || item.component,
  }
}
</script>

<template>
  <div v-editable="props.blok">
    <component
      v-for="item in props.blok.body"
      :key="item._uid"
      :is="getComponent(item).component"
      :blok="item"
    />
  </div>
</template>
Youhan commented 7 months ago

For me the trick was to explicitly import all the components that have useAsyncData in them. Have you tried that?

mrillusion commented 7 months ago

But only my [...slug].vue has useAsyncData then I pass this on to each component via props? Am I misunderstanding what you mean?

I haven't explicitly imported all components as it seemed like an anti-pattern when they are already autoimported but I'll give it a shot in the meantime. Thanks!

Youhan commented 7 months ago

Yeah it is really hard to tell without sharing working code. Is it possible for you to share a minimal reproduction code?

mrillusion commented 7 months ago

Update as of Nuxt 3.11.0 it is possible to define pages as clientside or serverside specifically.

This means changing my [...slug].vue to [...slug].server.vue is a applicable workaround the resolves this issue. Note this is only possible in Nuxt 3.11.0 and higher.

It's still likely worthwhile to have this issue looked into by Storyblok to ensure their docs work as expected without these redundant API requests.

alvarosabu commented 7 months ago

Thanks for the detailed explanation @mrillusion I will take a look

alvarosabu commented 6 months ago

Hi @mrillusion I just opened a PR to solve this when using useAsyncStoryblok, let me explain why this was happening.

The composable useAsyncStoryblok uses the useStoryblokApi(); under the hood, this means that it uses isomorphic-fetch

If you check the Nuxt docs on data fetching https://nuxt.com/docs/getting-started/data-fetching you will see the following warning

[!WARNING] Beware that using only $fetch will not provide network calls de-duplication and navigation prevention. It is recommended to use $fetch for client-side interactions (event based) or combined with useAsyncData when fetching the initial component data.

Since $fetch is similar to isomorphic-fetch is the reason why the duplication happens. So whenever you use the raw storybook API wrap it with useAsyncData in a similar way as I did on the PR for the composable useAsyncStoryblok

Hope this helps.

github-actions[bot] commented 6 months ago

:tada: This issue has been resolved in version 6.0.8 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

mrillusion commented 6 months ago

Hi @alvarosabu thank you for taking a look into this.

I have updated my Stackblitz reproduction to use storyblok-nuxt 6.0.10 and it appears the issue still exists with no change? Am I missing something?

https://stackblitz.com/edit/nuxt-storyblok-fetch-issue?file=package.json

alvarosabu commented 6 months ago

hi @mrillusion I would suggest trying locally? Stackblitz may do the network requests when generating the project.

I also added a StoryblokComponent to the [slug].vue template and getting the same result as the video

I attached a video of a statically generated Nuxt project, when you navigate to a route using useAsyncStoryblok the network call doesn't appear in the devtools anymore. Please let me know if it's not like that on your side.

https://github.com/storyblok/storyblok-nuxt/assets/4699008/645777f3-31ae-4eb4-af60-bccbe4596d92

mrillusion commented 6 months ago

Hi @alvarosabu thanks for your follow up. Downloading the stackblitz example to my local machine results in the same behaviour as shown directly in stackblitz (See attached gif).

CleanShot 2024-04-22 at 16 42 30

There must be a difference between your example and the reproduction I have shared via stackblitz, are you able to review the repoduction I have shared or provide your working example so I can review?

alvarosabu commented 6 months ago

Thanks, @mrillusion I'm going to download your stackblitz and see if I can find the reason.

alvarosabu commented 6 months ago

Hi @mrillusion I'm still investigating the issue, I can only reproduce it if I download your Stackblitz reproduction, I tested the same scenario (A page with a Teaser/Feature with a NuxtLink inside) on a project of mine and also a new fresh project and the error doesn't occur 🫠. I'm trying to figure out what's different between them.

What I noticed is that in your reproduction is that even if we follow Nuxt recommended useFetch for de-duplication on SSG and create the raw call without the Storyblok module, it has the same issue.

I also discovered that only happens if the navigation inside the blok component is a NuxtLink, if you change it for a normal anchor it doesn't happen.

alvarosabu commented 6 months ago

Hi, @mrillusion I finally have some updates, so I forked the Stackblitz reproduction you provided and removed the Storyblok Module completely to use Nuxt useFetch instead.

The issue persists, if you navigate using the NuxtLink it does a request. If you navigate with a normal anchor it doesn't.

It seems to be an upstream issue. I would suggest trying it out in a fresh nuxt install project since we weren't able to reproduce it anywhere but in the reproduction link you provided.

https://stackblitz.com/edit/nuxt-storyblok-fetch-issue-db6het?file=pages%2F%5B...slug%5D.vue

Screenshot 2024-04-25 at 10 34 56