Storyblok Raises $80M Series C - Read News

What’s the True Total Price of Enterprise CMS? Find out here.

Skip to main content

Announcing the Official Storyblok Richtext package

Developers
Alvaro Saburido
Try Storyblok

Storyblok is the first headless CMS that works for developers & marketers alike.

We are more than excited to share with you a brand new solution for rendering Richtext content in your projects:

The @storyblok/richtext package is a framework-agnostic, highly configurable, fully typed, and easily extendable resolver aimed to improve the developer experience of the ecosystem.

Let's explore how you can benefit from it.

How to use it

First of all, let's install @storyblok/richtext with your package manager of choice:

        
      npm install @storyblok/richtext
    

Now, we can import the richTextResolver function and use the render method to pass Richtext content from a story.

main.ts
        
      import { richTextResolver } from '@storyblok/richtext'

const { render } = richTextResolver()

const html = render(blok.content.richtextField)

// Insert it to your page
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
  <div>
  ${html}
  </div>
`
    

Pretty straightforward, right? Now, let's see how we can configure it further to get the best out of it.

Overwriting resolvers

You may want to customize the HTML returned by any of the resolvers available, such as:

  • Links
  • Image tags
  • Even emojis

Let's assume our goal is to add TailwindCSS classes to an anchor tag:

main.ts
        
      import { MarkTypes, NodeTypes, richTextResolver } from '@storyblok/richtext'
 
const html = richTextResolver({
  resolvers: {
    [MarkTypes.LINK]: (node) => {
      return `<a href="${node.attrs?.href}" target="${node.attrs?.target}" 
        class="text-blue-500 hover:text-blue-700 underline transition-colors duration-200 ease-in-out">
        ${node.children}
      </a>`;
    },
  },
}).render(doc);
    

Or maybe to exchange img tags with figure tags.

main.ts
        
      import { BlockTypes, NodeTypes, richTextResolver } from '@storyblok/richtext'

const html = richTextResolver({
  resolvers: {
    [BlockTypes.IMAGE]: (node) => {
      const src = node.attrs?.src;
      const alt = node.attrs?.alt || '';
       const caption = node.attrs?.caption || ''; // Optional caption

      return `
        <figure class="flex flex-col items-center">
          <img src="${src}" alt="${alt}" class="max-w-full h-auto rounded-lg shadow-md">
          ${caption ? `<figcaption class="mt-2 text-sm text-gray-600">${caption}</figcaption>` : ''}
        </figure>
      `;
    },
  },
}).render(doc);
    
learn:

BlockTypes, MarkTypes, and TextTypes enums are exported by the library as well.

Both can be done by passing an options object with a resolvers property to the richTextResolver method.

Optimizing Images

To optimize images in the Richtext, you can use the optimizeImages property on the richTextResolver options. For the full list of available options, check the Image Optimization documentation.

        
      import { richTextResolver } from '@storyblok/richtext'
 
const html = richTextResolver({
  optimizeImages: {
    class: 'black-and-white',
    filters: {
      grayscale: true,
    },
  },
}).render(doc)
    

Security

The @storyblok/richtext package offers a minimal layer of HTML character escaping by default but doesn't offer a full sanitization mechanism.

Since we care about security, we recommend you to always sanitize the HTML string output from the rich text resolver before injecting directly it into the DOM.

Here is an example of how you might sanitize HTML output using sanitize-html before rendering it to the DOM:


        
      import sanitizeHtml from 'sanitize-html';
import { richTextResolver } from '@storyblok/richtext';

const html = richTextResolver().render(yourRichTextContent);
const sanitizedHTML = sanitizeHtml(html, {
    allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img', 'figure', 'figcaption']),
    allowedAttributes: {
        ...sanitizeHtml.defaults.allowedAttributes,
        'img': ['src', 'alt', 'title']
    }
});

document.getElementById('your-element-id').innerHTML = sanitizedHTML;
    

For more info please visit the Security section of the official documentation.

Using a framework such as Vue or React

We've got you covered! You can extend the @storyblok/richtext to work with your favorite framework by passing the correspondent render function and types using Typescript Generics.

In this example, we are going to use Vue.

App.vue
        
      <script setup lang="ts">
import { VNode, createTextVNode, h } from 'vue'

const options : SbRichTextOptions<VNode> = {
  renderFn: h,
  textFn: createTextVNode,
}
const { render } = richTextResolver<VNode>(options)

const root = () => render(story.value.content.richtext)
</script>

<template>
   <root />
</template>
    

By default, the rich text package will parse resolvers to HTML strings for both render mark types and text nodes. Since each framework has its own objects instead of plain HTML:
- Vue uses VNodes
- React uses React Elements/JSX

You can now overwrite both by passing Vue's h and createTextVNode for rendering using renderFn and textFn, respectively. Furthermore, we ensure we have the correct type of support by passing the VNode type as a generic to the resolver.

What about the previous approach?

We will sunset it in due time, but to facilitate the migration, an adoption of the new approach, both solutions will co-exist in the ecosystem for a period of time until the next major version storyblok-js-client (v7.0).


What's more exciting about this, is that all the Storyblok OSS ecosystem will benefit from this new API, making it possible to add new features to the SDKs, such @storyblok/vue and @storyblok/react, with their own components and composables/hooks already integrating the Richtext functionality along with DX focus features, such automatically overwrite internal links for Vue's Router Links.

Stay alert for further announcements in the near future!

Next Steps

We hope you’re excited to try out this new package! We would absolutely love to see your projects built with Storyblok and hear your feedback about this latest tool.

Would you like to contribute to the development of @storyblok/richtext? Feel free to create an issue or a PR in the official GitHub repository.