Skip to content

Rendering Storyblok Rich Text in Astro

Rendering Rich Text elements in headless content management systems (CMS) like Storyblok can be challenging. We decided to carry out this process with Astro. Here is what we learned in the process.

Using an official integration

The Storyblok CMS has many integrations for various frontend frameworks, including React, Vue, and Svelte. Luckily for us, they also integrated it for Astro. Astro is the new kid on the block, with a vibrant and active community and a responsive developer team. These are just a few of the reasons that we chose Astro for website development.

Headless CMS platforms are designed to provide content through APIs, allowing developers to use that content in various applications and front-end frameworks. The most straightforward way of using Storyblok in the Astro framework is through its official storyblok-astro integration.

Rich Text elements often contain HTML tags, inline styles, and other formatting options that need to be properly rendered on the front end. Besides common WYSIWYG (“what you see is what you get”) editor capabilities, Storyblok is capable of allowing content editors to embed such elements as inline blocks (components), custom styling, emojis, quotes, and code snippets.

The official integration provides an easy way to render Rich Text by using the renderRichText function that comes with @storyblok/astro:

import { RichTextSchema, renderRichText } from “@storyblok/astro”;
import cloneDeep from “clone-deep”;
const mySchema = cloneDeep(RichTextSchema);
const { blok } = Astro.props;
const renderedRichText = renderRichText(blok.text, {
schema: mySchema,
resolver: (component, blok) => {
switch (component) {
case “my-custom-component”:
return `<div class=”my-component-class”>${blok.text}</div>`;
return `Component ${component} not found`;

A challenge

Although the renderRichText from @storyblok/astro works fine and covered our most basic needs, it quickly turned out to be limiting and problematic for the following reasons:

  1. The renderRichText utility cannot map Rich Text elements to actual Astro components and so cannot render embedded Storyblok components inside the Rich Text field in CMS.

  2. Links that you might want to pass through your app’s router cannot be reused because they require the actual function to be mapped with data.

    It is hard to maintain the string values, especially when complex needs arise — for example, when setting classes and other HTML properties dynamically. It may be possible to minimize the complexity by using some HTML parsers like ultrahtml, but that does not eliminate the problem entirely.

The solution

Instead of dealing with HTML markup, storyblok-rich-text-astro-renderer provides a capability to convert any Storyblok CMS Rich Text data structure into the nested component nodes structure — { component, props, content } — and render it with Astro. The configuration is easily extended to meet all project needs.

The package delivers:

  • The RichTextRenderer.astro helper component, which provides options to map any Storyblok Rich Text element to any custom component (for example, Astro, SolidJS, Svelte, or Vue).

  • The resolveRichTextToNodes resolver utility can potentially reuse the transform utility before rendering the structure manually.

Using the package

The usage of storyblok-rich-text-astro-renderer is simple, yet flexible:

import RichTextRenderer, { type RichTextType } from “storyblok-rich-text-astro-renderer/RichTextRenderer.astro”;
import { storyblokEditable } from “@storyblok/astro”;
export interface Props {
blok: {
text: RichTextType;
const { blok } = Astro.props;
const { text } = blok;
<RichTextRenderer content={text} {storyblokEditable(blok)} />

Sensible default resolvers for marks and nodes are provided out of the box. You only have to provide resolvers if you want to override the default behavior.

Use resolver to enable and control the rendering of embedded components, and schema to control how you want the nodes and marks to be rendered:

nodes: {
heading: ({ attrs: { level } }) => ({
component: Text,
props: { variant: `h${level}` },
paragraph: () => ({
component: Text,
props: {
class: “this-is-paragraph”,
marks: {
link: ({ attrs }) => {
const { custom, restAttrs } = attrs;
return {
component: Link,
props: {
link: { custom, restAttrs },
class: “i-am-link”,
resolver={(blok) => {
return {
component: StoryblokComponent,
props: { blok },


That’s all there is to it! We just made rendering Storyblok Rich Text in Astro much easier.

The storyblok-rich-text-astro-renderer package offers customization options and improves frontend development workflow, enabling you to tailor the rendering behavior to your project’s specific requirements.

Even though Astro supports numerous integrations of React, Svelte, and Vue, when you want to go with bare Astro, the storyblok-rich-text-astro-renderer package is the right choice.

About Version 2
Version 2 is one of the most dynamic IT companies in Asia. The company develops and distributes IT products for Internet and IP-based networks, including communication systems, Internet software, security, network, and media products. Through an extensive network of channels, point of sales, resellers, and partnership companies, Version 2 offers quality products and services which are highly acclaimed in the market. Its customers cover a wide spectrum which include Global 1000 enterprises, regional listed companies, public utilities, Government, a vast number of successful SMEs, and consumers in various Asian cities.

About NordLayer
NordLayer is an adaptive network access security solution for modern businesses – from the world’s most trusted cybersecurity brand, Nord Security.

The web has become a chaotic space where safety and trust have been compromised by cybercrime and data protection issues. Therefore, our team has a global mission to shape a more trusted and peaceful online future for people everywhere.



Click one of our contacts below to chat on WhatsApp