antfu-collective / vite-ssg

Static site generation for Vue 3 on Vite

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to hydrate subsequent routes correctly?

malcock opened this issue · comments

Hi, I've been trying to use this package to generate a site using a headless CMS.

I've used the includedRoutes function to pull a list of all my content to create all the routes, and then pulled the content from the API to generate each page. I can see the rendered HTML in the dist files.

To push the data into the pages, so far I've tried the following;

Using onServerPrefetch to pull the content, like so:

<script setup lang="ts">
///// abbreviated for clarity
const route = useRoute();
const story = ref(null);
const path = computed(() =>
  route.fullPath === "/" ? "/home" : route.fullPath
);

const getStory = async () => {
  const data = await axios
    .get(
      `https://api.storyblok.com/v1/cdn/stories${path.value}?token=${environment.storyblokKey}&cv=${environment.cv}`
    )
    .catch((err) => {
      console.log("error rendering page", path.value);
    });
  story.value = data.data.story;
};

onServerPrefetch(async () => {
  await getStory();
});
</script>

<template>
  <template v-if="story">
    <component
      v-if="story"
      :is="story.content.component"
      :key="story.content._uid"
      :blok="story.content"
      :theme="story.content.theme"
      :class="`theme-${story.content.theme}`"
    />
  </template>
</template>

This renders the HTML, but as soon as you load the page, the content is replaced as it was in the #app div and you end up with an empty page as the story ref is not populated

I've also tried pushing the content into the initialState and then hydrating from the store like so;

// main.ts
if (isClient) {
      console.log({ isClient, initialState });
      pinia.state.value = initialState.pinia || {};
    } else {
      if (routePath) {
        const store = useContentStore();
        await store.load(routePath === "/" ? "/home" : routePath); // pretty much the getStory method above
        initialState.pinia = pinia.state.value;
      }
      onSSRAppRendered(() => {
        initialState.pinia = pinia.state.value;
      });
    }
[...page.vue]
const content = useContentStore();
const story = ref<any>(content.story);

// same as previous

With this one, the HTML is rendered and unique INITIAL_STATE is placed on the generated pages - and the page remains once the Vue app loads and the content is rehydrated. The problem I have is that while the initial page loads, if I navigate to another route, the content of the page stays the same - as the state doesn't change from the original initialState. The only way I can make it load the correct content is to reload the page, or to navigate to it directly - as this obviously will bring a new initialState.

The only workaround I've come up with so far is to fore go using router-link tags and fall back to tags as that will essentially be the same as navigating directly to the page, however this doesn't feel proper and I lose stuff like router-link-active classes on my links.

Is there something I'm missing, or am I expecting this module to work in a way that it just doesn't? I can't figure out how I can use this to generate a multi page site that you can navigate around

Thanks for any help in advance!

Try using suspense / async setup instead of onServerPrefetch https://vuejs.org/guide/built-ins/suspense.html.