Skip to content

Instantly share code, notes, and snippets.

@SebbeJohansson
Created September 29, 2022 21:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SebbeJohansson/31b64c5c4f914abfec2660210ec7795a to your computer and use it in GitHub Desktop.
Save SebbeJohansson/31b64c5c4f914abfec2660210ec7795a to your computer and use it in GitHub Desktop.
In-Line Storyblok block rendering for Nuxt3 with SSR and Prerendering
<script setup lang="ts">
import { Richtext } from 'storyblok-js-client';
const props = defineProps({ blok: Object });
const nuxtApp = useNuxtApp();
const textObject = { ...props.blok.text };
const nodes = [];
// Proof of concept for custom handling of inline blok nodes.
Object.entries(textObject.content).forEach(([key, node]) => {
if (node.type === 'blok') {
const blok = {
content: node.attrs?.body?.[0],
};
nodes.push({
key,
type: 'blok',
content: {
blok,
},
});
} else {
nodes.push({
key,
type: 'html',
content: nuxtApp.$formatRichText(useStoryblokApi().richTextResolver.render({
type: 'doc',
content: [
node,
],
} as Richtext)),
});
}
});
</script>
<template>
<div v-editable="blok" class="text">
<div v-for="node in nodes" :key="node.key">
<component
:is="$resolveStoryBlokComponent(node.content.blok)"
v-if="node.type === 'blok'"
:blok="node.content.blok.content"
/>
<div v-else v-html="node.content" />
</div>
</div>
</template>
<style>
.text img {
max-width: 100%;
}
</style>
@jelmerdemaat
Copy link

@SebbeJohansson I think you mean @joezimjs :)

@SebbeJohansson
Copy link
Author

@SebbeJohansson I think you mean @joezimjs :)

100%! :D God damn autocomplete :P

@joezimjs
Copy link

@SebbeJohansson Yes, renderRichText does render deeply, but if it comes across a blok deep in there, it won't load this Vue component in order to render it as a component.

@SebbeJohansson
Copy link
Author

@joezimjs aaah i understand.
Should just be a case of infinite loop until it find the end no?

@joezimjs
Copy link

@SebbeJohansson No, it means you can't use renderRichText. You have to be able to render every kind of node yourself via a Vue component that is also able to traverse the rich text data structure and display each of its child nodes in the same way.

@joezimjs
Copy link

@marvr/storyblok-rich-text-vue-renderer Does this, but it hasn't been touched in a while and I'm not sure if there's a good way to set it up for Nuxt. I used it with iles but I had to register each component that could be used in it manually like this:

import { defineApp } from 'iles'
import { StoryblokVue, apiPlugin } from '@storyblok/vue';
import { plugin as VueRichTextRenderer, RichTextRenderer, defaultResolvers } from '@marvr/storyblok-rich-text-vue-renderer';
import { h, VNode } from 'vue';

import Grid from "@/components/Grid.vue";
import Page from "@/components/Page.vue";
import Teaser from "@/components/Teaser.vue";
import Feature from "@/components/Feature.vue";

export default defineApp({
    enhanceApp({app}) {
        /*
         * Register components for automatic import for use in the rich text renderer and StoryblokComponent
         * since they can't just use unplugin-vue-components
         */

        // LIST ALL COMPONENTS HERE 👇
        const components: Record<string, any> = {
            Grid, Page, Teaser, Feature, RichTextRenderer
        }

        // List of components for use by rich text renderer. (generated later)
        const componentRenderers: Record<string, (data: any) => VNode> = {}

        // The render function used by the rich text renderer for our components
        function componentRenderer ({id, component, _uid, fields} : {id:string, component: string, _uid: string, fields: Record<string, any>}) {
            return h(components[component], {blok:{id, component, _uid, ...fields}})
        }

        // Iterate through all the component we listed so we can register them
        Object.entries(components).forEach(([name, component]) => {
            // Register component for use inside other components without needing to import
            app.component(name, component)

            // Register component for use by rich text renderer
            componentRenderers[name] = componentRenderer
        })

        app.use(VueRichTextRenderer({
            resolvers: {
                ...defaultResolvers,
                // 👇 list of component renderers we just generated
                components: componentRenderers
            }
        }))
    },
})

I'm honestly REALLY surprised there hasn't been an official library/plugin that does this at this point. It's an extremely common use case and no one has an amazing solution.

@SebbeJohansson
Copy link
Author

@joezimjs Have you tried raising a feature request specifically for this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment