vue.jsnuxt.jsserver-side-renderingstatic-site-generation

Nuxt static not loading fetched state when pushing new route


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


Solution

  • 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.