Andre M
Andre M

Reputation: 7554

Vue content modified after serverPrefetch on client side, when using SSR

I am working with Vue, by means of Quasar, with the pages being rendered via SSR. This works well enough, but I have a component that doesn't seem to behaving properly.

The issue is that the content is rendered correctly on the server side (verified by checking network log in Chrome), with the axios call loading in the data into an element using v-html, but when we get to the browser the state seems to be reset and server side rendered content gets lost, when using the 'elements' tab in the inspector.

Any ideas?

The Vue component is as follows:

<template>
  <div class="dy-svg" v-html="svgData"></div>
</template>

<script>
/**
 * This provides a way of loading an SVG and embedding it straight into
 * the page, so that it can have css applied to it. Note, since we are
 * using XHR to load the SVG, any non-local resource will have to deal
 * with CORS.
 */
import axios from 'axios';

export default {
  props: {
    src: String,
    prefetch: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      svgData: undefined,
    };
  },
  async serverPrefetch() {
    if (this.prefetch) {
      await this.loadImage();
    }
  },
  async mounted() {
    // if (!this.svgData) {
    //   await this.loadImage();
    // }
  },
  methods: {
    async loadImage() {
      try {
        let url = this.src;

        if (url && url.startsWith('/')) {
          url = this.$appConfig.baseUrl + url;
        }

        const response = await axios.get(url);
        let data = response.data;

        const idx = data.indexOf('<svg');
        if (idx > -1) {
          data = data.substring(idx, data.length);
        }
        this.svgData = data;
      } catch (error) {
        console.error(error);
      }
    }
  }
};
</script>

Note, I did try add the v-once attribute to the div, but it seems to have no impact.

Environment:

Upvotes: 1

Views: 3441

Answers (1)

W.M.
W.M.

Reputation: 786

The fetched data needs to live outside the view components, in a dedicated data store, or a "state container". On the server, you should pre-fetch and fill data into the store while rendering. For this you can use Vuex.

Example Vuex store file:

import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
// import example from './module-example'

Vue.use(Vuex)

export default function ( /* { ssrContext } */ ) {
  const Store = new Vuex.Store({
    state: () => ({
      entities: {}
    }),
    actions: {
      async get({
        commit
      }) {
        await axios.get('https://example.com/api/items')
          .then((res) => {
            if (res.status === 200) {
              commit('set', res.data.data)
            }
          })
      }
    },

    mutations: {
      set(state, entities) {
        state.entities = entities
      },
    },

    modules: {},

    // enable strict mode (adds overhead!)
    // for dev mode only
    strict: process.env.DEV
  })

  return Store
}

Example Vue page script:

export default {
  name: 'PageIndex',

  computed: {
    // display the item from store state.
    entities: {
      get() {
        return this.$store.state.entities
      }
    }
  },

  serverPrefetch() {
    return this.fetchItem()
  },

  mounted() {
    if (!this.entities) {
      this.fetchItem()
    }
  },

  methods: {
    fetchItem() {
      return this.$store.dispatch('get')
    }

  }
}

This should solve the issue you're facing.

Upvotes: 0

Related Questions