4

I have a Nuxt 3 application that gets page content from an external CMS via GraphQL. The content that I get is totally dynamic, so I have to use dynamic components to render my content.

For example if I query getContentFromPath with the paramter {path: '/'} it would return something like this:

getContentFromPath: {
  id: 'abc123',
  dynamicContent: [
    {
      id: 'xyz123',
      cmsComponent: 'RichText',
      data: 'here would be richtext specific data like some html'
    }
  ]
}

So based on what the content manager maintained in the CMS, the content that I query could change. It could be different components like RichText, Image and so on. Therefore I have a generic Nuxt page [...pages].vue where all my routes get handled. I am using apollo to get my data like so (simplified):

<script setup>
import { useQuery } from '@vue/apollo-composable';
import { getContentFromPath } from './graphql/queries';

const cmsComponent = ref('');
const contentFromPath = await useQuery(getContentFromPath, {
  path: '/',
});
contentFromPath.onResult((res) => (cmsComponent.value = res.data.getContentFromPath.dynamicContent[0].cmsComponent));
</script>
<template>
  <component :is="cmsComponent" />
</template>

Here I am facing the hydration problem. I have to wait for my CMS content to be returned to my Nuxt application. Then I know which components to render. The components could also query data (e.g. a blog-list-component). The Nuxt server part queries the data but the client doesn't know about this and rerenders / rehydrates as soon as everything is loaded.

With more complex components from the CMS it can happen, that the page flashes because some components get rerendered faster. Using <client-only> is not an option because the content inside these components are relevant for SEO. What I am searching for is a solution that gets the data on the server side, prepares all the components and then renders it on the client side.

So my questions are: How do I deal with hydration in my case? Is it even possible or is Nuxt the wrong Framework for my use-case?

P.S. I already read the article from Alexander Lichter https://blog.lichter.io/posts/vue-hydration-error/

If something isn't clear, please let me know.

Der Alex
  • 718
  • 3
  • 16

1 Answers1

0

Great question! Based on your data structure you'll need to loop over dynamicConent, render out the individual components and bind the props to each component. This can be accomplished using v-bind and v-for.

<script setup>
import { useQuery } from '@vue/apollo-composable';
import { getContentFromPath } from './graphql/queries';

const cmsComponents = ref([]);
const contentFromPath = await useQuery(getContentFromPath, {
  path: '/',
});

// We might eventually want to "await" here too?
contentFromPath.onResult((res) => (cmsComponents.value = res.data.getContentFromPath));

</script>
<template>
  <component 
    v-for="(component) in cmsComponents"
    :key="key.id"
    :is="component.cmsComponent"
    v-bind="component"
  />
</template>

In order to address the hydration issue we can look at prior art. Here part of the source from the the nuxt3-vuex module

import { createStore } from 'vuex'
import { defineNuxtPlugin } from '#app'
import VuexStore from '#build/vuexStore.js'

export default defineNuxtPlugin((nuxtApp) => {
  const store = createStore(VuexStore)
  nuxtApp.vueApp.use(store)

  if (process.server) {
    nuxtApp.payload.vuex = store.state
  } else if (nuxtApp.payload && nuxtApp.payload.vuex) {
    store.replaceState(nuxtApp.payload.vuex)
  }

  return {
    provide: {
      store,
    },
  }
})

As you can see that module provides a plugin a that syncs the state between the client and the server by using a store. I would suggest following that approach.

bitbyte
  • 1
  • 1