Choosing the right call for Plugin to dynamically generate JSON-LD (vue.js,nuxt.js)

172 views Asked by At

I am tryin to create an FAQ component in Nuxt.js that dynamically generates JSON-LD scripts for structured data. The structured data represents an FAQ page with questions and answers.https://developers.google.com/search/docs/appearance/structured-data/faqpage

My current Problem is that it's called too early I think and the content of my component is not attached.

export default defineNuxtPlugin((nuxtApp) => {
    const metaLdScripts = []

    nuxtApp.$addLdScript = (script) => metaLdScripts.push(script)

    const attachLdScripts = () => {
        const head = document.getElementsByTagName('head')[0]

        const scriptTag = document.createElement('script')

        scriptTag.type = 'application/ld+json'
        scriptTag.text = `{
            '@context': 'https://schema.org',
            '@type': 'FAQPage',
            mainEntity: ${JSON.stringify(metaLdScripts)},
        }`

        head.appendChild(scriptTag)
    }
    
    nuxtApp.hook('page:finish', () => {
        attachLdScripts()
    })
})

This plugins is called in a different component and it should be filled with it's data.

<template>
    <div v-editable="blok" class="c-item c-item-collapse">
        <button @click="toggleCollapse" class="c-item-collapse__header">
            <div class="c-item-collapse__title">
                <ui-headline size="0" :tagName="blok.title_headline">
                    {{ blok.title }}
                </ui-headline>
            </div>
            <div class="c-item-collapse__actions">
                <ui-icon v-if="!isCollapsed" icon="circle-minus" />
                <ui-icon v-else icon="circle-plus" />
            </div>
        </button>
        <div></div>

        <div v-show="!isCollapsed" class="c-item-collapse__content">
            <StoryblokComponent
                v-for="blok in blok.items"
                :key="blok._uid"
                :blok="blok"
            />
        </div>
    </div>
</template>

<script setup>
import { ref } from 'vue'

const { $addLdScript } = useNuxtApp()

const isCollapsed = ref(true)

function toggleCollapse() {
    isCollapsed.value = !isCollapsed.value
}

const { blok } = defineProps({ blok: Object })

onMounted(() => {
    $addLdScript({
        '@type': 'Question',
        name: blok.title,
        acceptedAnswer: {
            '@type': 'Answer',
            text: blok.items.map(
                (item) => item.body?.content[0]?.content[0]?.text
            ),
        },
    })
})
</script>

<style lang="scss">
.c-item-collapse {
    @apply p-8 bg-white rounded-xl md:rounded;

    .ui-section--bg-white & {
        // box-shadow: 0px 0px 25px 0px #22222214;
        @apply bg-gray-light;
    }

    &__header {
        @apply flex justify-between items-center text-graphite w-full;
    }

    &__actions {
        @apply flex items-center;
    }

    &__title {
        @apply font-black font-barlow text-left text-sm md:text-base;
    }

    &__content {
        @apply pt-4 text-xs md:text-base;
    }
}

.c-item-collapse + .c-item-collapse {
    @apply mt-2 md:mt-4;
}
</style>

Output:

 <script type="application/ld+json">{
            '@context': 'https://schema.org',
            '@type': 'FAQPage',
            mainEntity: [],
        }</script>

I am using a custom plugin because I don't want to have several application/ld+json script tags in the Head for the Item-crawler component since it's all going to be FAQPages and kinda mess up the Google Rich Text Result schema. I tried using the https://github.com/ymmooot/nuxt-jsonld/blob/master/README.md calling the module in different render times, but that didn't work. It still makes separate script tags for each item crawler component in the Page.

I am pretty sure the problem is the timing. After everything is rendered the plugin should be called and executed, I think just setting a timer would be quite some bad practice Ig.I tried nearly all lifecycle hooks presented in the vue docs.

0

There are 0 answers