5

I'm generating full static web app using nuxt as described here https://nuxtjs.org/blog/going-full-static/#crazy-fast-static-applications

I have a small blog to load as static site also, so I'm using the fetch hook to load the data from api.

async fetch() {
  this.posts = await fetch(`${this.baseApi}/posts`).then(res => res.json())
},

When I generate (npm run generate), the fetched state is properly generated inside the dist/assets/static, so when directly accessing /blog, the state is properly loaded and the data displays correctly. However, when I'm in the homepage, and access the blog using a

this.$router.push

or a

<nuxt-link to="/blog">Blog</nuxt-link>

The fetched state does not get loaded, and I have to call the api again, or call this.$fetch() one more time in the mounted() hook

I have already added a

watch: {
  '$route.query': '$fetch'
}

to the homepage

I need the fetched state to be properly loaded when using navigation What am I still missing ?

Clarification

I'm not experiencing any problem with the fetch hook by itself, but rather with the navigation not retrieving the state of the target route. Even the HTML is there I need the page to get the state of the target route, when the route changes, because the vue template depends on it, so if it's not loaded, the ui won't display anything, and i'm forced to call the fetch hook manually

For a clearer view, This is a screenshot of my devtools while directly accessing /blog, notice how state.js is properly retrieved (it contains all rendered content) State correctly fetched when directly accessing

And the following is a screenshot of my devtools while accessing /, and then going to blog using nuxt-link, or a this.$router.push (same result)

State not fetched after navigation

Static state screenshot: Static state.js of /blog

Blog.vue

<template>
  <b-container class="container blog">
    <b-row>
      <b-col lg="12" md="12" sm="12" cols="12" class="logo-col">
        <SbLogoSingle />
      </b-col>
    </b-row>
    <b-row v-if="$fetchState.pending" class="text-center">
      <b-spinner style="margin: auto"></b-spinner>
    </b-row>
    <b-row v-else>
      <b-col
        v-for="(post, idx) in posts.data"
        :key="idx"
        lg="4"
        md="4"
        sm="6"
        cols="12"
        class="blog-post-col"
      >
        <b-card
          v-if="post !== undefined"
          no-body
          class="shadow-lg blog-post-card"
          :img-src="post.media.url"
          img-top
        >
          <b-card-body class="text-left">
            <b-card-title>{{ replaceSlugByString(post.slug) }}</b-card-title>
            <b-card-text
              class="post-short-description"
              v-html="post.localizations[0].shortDescription"
            ></b-card-text>
          </b-card-body>
          <template #footer>
            <div class="text-left">
              <b-button class="apply-btn read-more-btn" @click="openBlogPost(idx)">Read more</b-button>
            </div>
          </template>
        </b-card>
      </b-col>
    </b-row>
  </b-container>
</template>

<script>
import { mapState } from 'vuex'

export default {
  data() {
    return {
      slug: 'test',
      posts: {},
      currentPage: 1,
      perPage: 12,
      pageIndex: 1,
      totalPages: 1,
    }
  },
  async fetch() {
    const response = await fetch(`${this.baseApi}/StaticPage`)
    const fetchedPosts = await response.json()

    this.posts = fetchedPosts
    // this.posts = await fetch(`${this.baseApi}/StaticPage`).then(res =>res.json())
  },
  computed: {
    ...mapState('modules/settings', ['baseApi']),
  },
  beforeMount() {
    this.$fetch() // i want to remove this because the pages are statically generated correctly, I'm only adding it to refresh the state. which can be retrieved as a separate js file when accessing the route directly
  },
  methods: {
    openBlogPost(idx) {
      const pageObject = this.posts.data[idx]
      this.$router.push({
        name: `blog-slug`,
        params: {
          slug: pageObject.slug,
          page: pageObject,
        },
      })
    },
    replaceSlugByString(slug) {
      return slug.replaceAll('-', ' ')
    },
  },
}
</script>

And here is the pastebin for slug.vue

https://pastebin.com/DmJa9Mm1

mysticalnetcore
  • 181
  • 1
  • 11
  • Not sure that `'$route.query': '$fetch'` will be useful here. Also, you're using `$fetch` and `fetch`, be careful of not mixing them both. – kissu Jun 07 '21 at 23:13
  • Correct, it's not useful. – mysticalnetcore Jun 08 '21 at 21:40
  • Pretty difficult to see what is the issue without more of the `Blog.vue` file itself. Also, install the Vue devtools to be able to debug your state more easily. – kissu Jun 08 '21 at 21:53
  • blog.vue: https://pastebin.com/2tQMq7t7 . I don't think it's useful to include screenshots of the vue dev tools. The problem is clear, the state is only fetched from the static folder if you access the url directly. Is this is the expected behavior? – mysticalnetcore Jun 08 '21 at 22:11
  • Especially that vue dev tools work in dev mode (server side rendering where the $fetch hook is always called automatically before mounting, and the state.js is never fetched from the static folder) -- completely different story not related to my problem, that's why i didn't post screenshots of the vue dev tools. – mysticalnetcore Jun 08 '21 at 22:20
  • Do you have another interesting `.vue` file to share? Like a `/blog/:slug` ? – kissu Jun 08 '21 at 22:45
  • I've updated my answer. – kissu Jun 09 '21 at 00:05
  • I have added the slug.vue – mysticalnetcore Jun 09 '21 at 17:45

2 Answers2

1

EDIT:

  • fetch() hook is working great, even if you come to this specific page for the first time, it will be triggered
  • Vue devtools can help you find out if some state is missing or behaving in a weird manner.
  • there is no such thing as state in static folder since the state is not a static variable or thing at all, it's dynamic and available only at runtime.
  • this answer may help you see a working example with JSONplaceholder (with a list + details pages): How to have list + details pages based on API fetched content

Try to not mix async/await and then.
So, this syntax should be more suited.

async fetch() {
  const response = await fetch(`${this.baseApi}/posts`)
  const fetchedPosts = await response.json()
  console.log('posts', fetchedPosts)
  this.posts = fetchedPosts
},

Then, you could debug with the network tab of the devtools to see if it is triggered. But I think that it should be fine then.


This answer that I just wrote more in-depth could also help understanding a bit more the fetch() hook: https://stackoverflow.com/a/67862314/8816585

kissu
  • 40,416
  • 14
  • 65
  • 133
  • I tried this and it didn't work. I'm not experiencing problems with the fetch hook by itself, but rather with the navigation not retrieving the updated state of the target route, I have added a clarification and some screenshots to my question. Thanks – mysticalnetcore Jun 08 '21 at 21:39
  • 1
    I do extensive research before asking, YES there is a static state generated for each route (I edited my question and included a screenshot in my question `Static State Screenshot`), because, isn't the point of static generation to no longer call fetch and asyncdata hooks ? (as explained here https://nuxtjs.org/blog/going-full-static/ ). I did not want to move the blog data to the store in order to not have them load inside the initial state.js. I will look into your JSONplaceholder suggestion – mysticalnetcore Jun 09 '21 at 17:43
  • I managed to achieve what I needed which is no longer needed to call the $fetch to update the component's UI, by moving the data to the store, a separate file blog.js, I'm adding my definite answer, Thanks for all the help – mysticalnetcore Jun 09 '21 at 19:53
  • Your contribution is very appreciated, but unfortunately did not help, since i didnt originally have any problem with the fetch hook. You were semi-right about the state.js though, it gets loaded only once, but there's a payload.js that gets loaded when the nuxt-link becomes visible, this payload.js fills the state with data taken from the store.. I'm adding everything to my answer – mysticalnetcore Jun 09 '21 at 20:00
  • 1
    I answered my question, if you are interested in knowing how i solved it. – mysticalnetcore Jun 09 '21 at 20:32
0

When generating a static website with nuxt using nuxt generate, you use the fetch hook to load the data once, and never have to load it again in your site.

You might encounter a moment where you have a properly generated html page, but with empty data, even though you can see the content in the html source, and the empty data causes the UI to not load, and forces you to re-hit the api (or manually calling the $fetch hook), to reload your state (and your UI)

In this case, move your data to the store, in my case I created a new store/modules/blog.js file:

export const state = () => ({
   posts:[]
})
export const mutations = {
   SET_POSTS(state, posts) {
       state.posts = posts
   }
}

Then modify your fetch hook to this:

async fetch() {
    const response = await this.$axios.$get(`${this.baseApi}/posts`)
    this.$store.commit("modules/blog/SET_POSTS",response)
}

You may discard the this.$axios, and use fetch it doesn't matter.

Then, after you run npm run generate, take a look at your dist/assets/static/<someid>/state.js you will find inside it all the state for the home page (my homepage doesn't include the blog posts) so I read modules:{blog:{posts:[]}... empty array

go to your dist/assets/static/<someid>/blog/state.js, and you should find all your posts loaded from the api there modules:{blog:{posts:{success:am,code:an ... There is also a dist/assets/static/<someid>/blog/payload.js

Now, when you visit your home page, the payload.js of the blog will be fetched when the <nuxt-link to='/blog'> becomes visible, and your state will be updated with the already fetched data

Now if you directly visit /blog the state.js will be retrieved before fetching payload.js, and your state will be the up to date

This is how you create a small static blog without ever hitting the API. Hope this is helpful.

mysticalnetcore
  • 181
  • 1
  • 11
  • Hey! I'm facing with a similar problem. When I static generate the site with `asyncData` I can see the payload files generated, but navigating to that page it makes the API calls to the server. Is your `store` version the way to work really statically? – Snsxn Sep 27 '21 at 14:34
  • I did not get the question, but the solution for me, was to move all needed state data to the store, and let the page read data directly from the stored state – mysticalnetcore Oct 05 '21 at 12:29
  • @Snsxn could you fix it? I can make it generate content with asyncData, it simply wont work... – Daniel Vilela Jun 25 '22 at 14:05
  • @DanielVilela no, instead I've refactored everything to work with the new `fetch()` – Snsxn Jul 22 '22 at 08:12
  • Its important to set a key when using asycnData, otherwise nuxt will not know if to call the same api or not. The problem that i am having is that that the fetch command works when i do npm run generate, but the payload files fail because the pre-rendering crawler is looking for the data almost immediately. – Ricky-U Sep 27 '22 at 07:01