iTranslated by AI
Introducing a library for elegantly processing microCMS Rich Editor content
Introduction
As those using microCMS are likely aware, the "Rich Editor" is one of the input forms available when configuring API schemas.

As shown here, it is an editor with various options. The data is returned as HTML, which can cover many use cases on its own.
However, I personally rarely use the retrieved HTML data as-is; instead, I often process it on the server before including it in the client build.
Since it felt like I was writing the same processing logic every time, I generalized the process to some extent, created a library, and published it. Please feel free to use it.
Features
This section introduces the features of this library.
About HTML data processing functions
- img tag
- Lazy loading (currently supports class names for the
lazysizeslazy loading library) - Responsive image support (technology to deliver optimal images according to window width using srcSet and sizes attributes)
- Placeholder image settings
- Adding imgix parameters
- Automatic setting of width and height attributes
- Lazy loading (currently supports class names for the
- iframe tag
- Lazy loading (currently supports class names for the
lazysizeslazy loading library) - Responsive support
- Lazy loading (currently supports class names for the
- code tag inside pre tag
- Adding class names for syntax highlighting (currently supports
highlight.js)
- Adding class names for syntax highlighting (currently supports
- Common
- Adding class names
- Adding arbitrary attribute values
About automatic generation of other data from HTML
- Table of Contents list creation
Usage
First, install it.
npm i microcms-richedit-processer
# yarn add microcms-richedit-processer
I will explain using Next.js here. If you are using another framework, please adapt it accordingly.
Since we want to process it at build time, we use getStaticProps.
import { GetStaticProps, NextPage } from "next";
import { createTableOfContents, processer } from "microcms-richedit-processer";
type Props = {
body: string;
toc: {
id: string;
text: string;
name: string;
}[];
};
export const getStaticProps: GetStaticProps<Props> = async () => {
const { contents } = await fetch(
"https://{SERVICE_ID}.microcms.io/api/v1/{ENDPOINT}",
{
headers: {
"X-API-KEY": "{API_KEY}",
},
}
).then((res) => res.json());
// Assuming HTML data is retrieved in contents.body.
return {
props: {
body: await processer(contents.body),
// To pass options
// body: processer(contents.body, {}),
toc: createTableOfContents(contents.body),
// To pass options
// toc: createTableOfContents(contents.body, {}),
},
};
};
Two functions are imported.
processer is responsible for processing HTML data.
createTableOfContents is responsible for creating a table of contents list.
Just use the string data processed this way for rendering on the client side!
Operational Details
The following sections explain each operation in detail.
Common Options
I will introduce common options that can be specified for each element, using the img tag as an example.
Adding class names
processer(content, { img: { addClassName: ["class01", "class02"] } });
<img
- src="https://sample.com/image.png"
alt
+ width="Width of the image is automatically inserted"
+ height="Height of the image is automatically inserted"
+ class="class01 class02 lazyload"
+ data-src="https://sample.com/image.png?auto=format"
+ data-srcset="https://sample.com/image.png?auto=format&w=640 640w, https://sample.com/image.png?auto=format&w=750 750w, https://sample.com/image.png?auto=format&w=828 828w, https://sample.com/image.png?auto=format&w=1080 1080w, https://sample.com/image.png?auto=format&w=1200 1200w, https://sample.com/image.png?auto=format&w=1920 1920w, https://sample.com/image.png?auto=format&w=2048 2048w, https://sample.com/image.png?auto=format&w=3840 3840w"
+ data-sizes="100vw"
/>
Adding arbitrary attribute values
processer(content, {
img: { addAttributes: { "aria-label": "sampleLabel", "data-id": "dataid" } },
});
<img
- src="https://sample.com/image.png"
alt
+ aria-label="sampleLabel"
+ data-id="dataid"
+ width="Width of the image is automatically inserted"
+ height="Height of the image is automatically inserted"
+ class="lazyload"
+ data-src="https://sample.com/image.png?auto=format"
+ data-srcset="https://sample.com/image.png?auto=format&w=640 640w, https://sample.com/image.png?auto=format&w=750 750w, https://sample.com/image.png?auto=format&w=828 828w, https://sample.com/image.png?auto=format&w=1080 1080w, https://sample.com/image.png?auto=format&w=1200 1200w, https://sample.com/image.png?auto=format&w=1920 1920w, https://sample.com/image.png?auto=format&w=2048 2048w, https://sample.com/image.png?auto=format&w=3840 3840w"
+ data-sizes="100vw"
/>
img tag processing
Next, I'll introduce the processing of img tags.
First, the default behavior.
<img
- src="https://sample.com/image.png"
alt
+ data-src="https://sample.com/image.png?auto=format"
+ data-srcset="https://sample.com/image.png?auto=format&w=640 640w, https://sample.com/image.png?auto=format&w=750 750w, https://sample.com/image.png?auto=format&w=828 828w, https://sample.com/image.png?auto=format&w=1080 1080w, https://sample.com/image.png?auto=format&w=1200 1200w, https://sample.com/image.png?auto=format&w=1920 1920w, https://sample.com/image.png?auto=format&w=2048 2048w, https://sample.com/image.png?auto=format&w=3840 3840w"
+ data-sizes="100vw"
+ width="Width of the image is automatically inserted"
+ height="Height of the image is automatically inserted"
+ class="lazyload"
/>
As shown above, the value specified in the src attribute is converted to a data-src attribute, and the auto parameter is set to format.
Details of the auto parameter
The width and height are then retrieved from the image URL specified in the src attribute and assigned.
Please rest assured that if you specify image sizes within the rich editor, those values will be set in the width and height attributes.
For responsive image settings, the default array of values [640, 750, 828, 1080, 1200, 1920, 2048, 3840] is used.
Based on these, URLs for each image size are created, allowing you to deliver images optimized for various device sizes without having to think about it.
Responsive Image Options
You can change the device sizes referenced when generating srcset. You can also change the sizes attribute.
processer(content, {
img: { deviceSizes: [640, 1280], sizes: "(min-width: 640px) 1000px, 100vw" },
});
<img
- src="https://sample.com/image.png"
alt
+ data-src="https://sample.com/image.png?auto=format"
+ data-srcset="https://sample.com/image.png?auto=format&w=640 640w, https://sample.com/image.png?auto=format&w=1280 1280w"
+ data-sizes="(min-width: 640px) 1000px, 100vw"
+ width="Width of the image is automatically inserted"
+ height="Height of the image is automatically inserted"
+ class="class01 class02 lazyload"
/>
imgix Parameters
In microCMS, you can use the imgix API when retrieving images. This option allows you to set the parameters to be specified at that time.
It uses ts-imgix internally, and since editor completion works, you can specify them with a very low risk of typos.
processer(content, { img: { parameters: { q: 50, w: 800, h: 600 } } });
<img
- src="https://sample.com/image.png"
alt
+ width="800"
+ height="600"
+ data-src="https://sample.com/image.png?auto=format&w=800&h=600&q=50"
+ data-srcset="https://sample.com/image.png?auto=format&w=640&h=480&q=50 640w, https://sample.com/image.png?auto=format&w=750&h=563&q=50 750w, https://sample.com/image.png?auto=format&w=828&h=621&q=50 828w, https://sample.com/image.png?auto=format&w=1080&h=810&q=50 1080w, https://sample.com/image.png?auto=format&w=1200&h=900&q=50 1200w, https://sample.com/image.png?auto=format&w=1920&h=1440&q=50 1920w, https://sample.com/image.png?auto=format&w=2048&h=1536&q=50 2048w, https://sample.com/image.png?auto=format&w=3840&h=2880&q=50 3840w"
+ data-sizes="100vw"
+ class="lazyload"
/>
Placeholder Image Settings
You can choose whether to set an alternative image to be displayed until the image is loaded. By combining this feature with the blur-up plugin of lazysizes, you can reduce stress even for users with slow internet connections.
processer(content, { img: { placeholder: true } });
<img
- src="https://sample.com/image.png"
alt
+ width="Width of the image is automatically inserted"
+ height="Height of the image is automatically inserted"
+ data-srcset="https://sample.com/image.png?auto=format&w=640 640w, https://sample.com/image.png?auto=format&w=750 750w, https://sample.com/image.png?auto=format&w=828 828w, https://sample.com/image.png?auto=format&w=1080 1080w, https://sample.com/image.png?auto=format&w=1200 1200w, https://sample.com/image.png?auto=format&w=1920 1920w, https://sample.com/image.png?auto=format&w=2048 2048w, https://sample.com/image.png?auto=format&w=3840 3840w"
+ data-lowsrc="https://sample.com/image.png?w=50&q=30&blur=10"
+ data-sizes="100vw"
+ class="lazyload"
/>'
Disabling Lazy Loading
Since it automatically configures responsive image settings and the width and height attributes, this library is effective even if you do not use lazy loading.
processer(content, { img: { lazy: false } });
After processing
<img
- src="https://sample.com/image.png"
+ src="https://sample.com/image.png?auto=format"
+ srcset="https://sample.com/image.png?auto=format&w=640 640w, https://sample.com/image.png?auto=format&w=750 750w, https://sample.com/image.png?auto=format&w=828 828w, https://sample.com/image.png?auto=format&w=1080 1080w, https://sample.com/image.png?auto=format&w=1200 1200w, https://sample.com/image.png?auto=format&w=1920 1920w, https://sample.com/image.png?auto=format&w=2048 2048w, https://sample.com/image.png?auto=format&w=3840 3840w"
+ sizes="100vw"
alt
+ width="Width of the image is automatically inserted"
+ height="Height of the image is automatically inserted"
/>
iframe tag processing
By default, it determines the aspect ratio based on the width and height of the iframe and makes the size responsive to the width of the parent element.
+ <div style="position: relative; padding-bottom: calc(480 / 854 * 100%);">
<iframe
- class="embedly-embed"
+ class="embedly-embed lazyload"
- src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FO1bhZgkC4Gw%3Ffeature%3Doembed&display_name=YouTube&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DO1bhZgkC4Gw&image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FO1bhZgkC4Gw%2Fhqdefault.jpg&key=d640a20a3b02484e94b4b0a08440f627&type=text%2Fhtml&schema=youtube"
width="854"
height="480"
scrolling="no"
title="YouTube embed"
frameborder="0"
allow="autoplay; fullscreen"
allowfullscreen="true"
+ data-src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FO1bhZgkC4Gw%3Ffeature%3Doembed&display_name=YouTube&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DO1bhZgkC4Gw&image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FO1bhZgkC4Gw%2Fhqdefault.jpg&key=d640a20a3b02484e94b4b0a08440f627&type=text%2Fhtml&schema=youtube"
+ style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;"
></iframe>
+ </div>
Specifying width and height
If you want to overwrite the width and height attributes of the iframe set in microCMS, please use this option. It will make it responsive based on those values.
processer(content, { iframe: { width: 960, height: 640 } });
+ <div style="position: relative; padding-bottom: calc(640 / 960 * 100%);">
<iframe
- class="embedly-embed"
+ class="embedly-embed lazyload"
- src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FO1bhZgkC4Gw%3Ffeature%3Doembed&display_name=YouTube&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DO1bhZgkC4Gw&image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FO1bhZgkC4Gw%2Fhqdefault.jpg&key=d640a20a3b02484e94b4b0a08440f627&type=text%2Fhtml&schema=youtube"
- width="854"
+ width="960"
- height="480"
+ height="640"
scrolling="no"
title="YouTube embed"
frameborder="0"
allow="autoplay; fullscreen"
allowfullscreen="true"
+ data-src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FO1bhZgkC4Gw%3Ffeature%3Doembed&display_name=YouTube&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DO1bhZgkC4Gw&image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FO1bhZgkC4Gw%2Fhqdefault.jpg&key=d640a20a3b02484e94b4b0a08440f627&type=text%2Fhtml&schema=youtube"
+ style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;"
></iframe>
+ </div>
Disabling lazy loading
Since it automatically makes the size responsive to the width of the parent element, this library is effective even if you do not use lazy loading.
processer(content, { iframe: { lazy: false } });
+ <div style="position: relative; padding-bottom: calc(480 / 854 * 100%);">
<iframe
class="embedly-embed"
src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FO1bhZgkC4Gw%3Ffeature%3Doembed&display_name=YouTube&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DO1bhZgkC4Gw&image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FO1bhZgkC4Gw%2Fhqdefault.jpg&key=d640a20a3b02484e94b4b0a08440f627&type=text%2Fhtml&schema=youtube"
width="854"
height="480"
scrolling="no"
title="YouTube embed"
frameborder="0"
allow="autoplay; fullscreen"
allowfullscreen="true"
+ style="position: absolute; width: 100%; height: 100%; top: 0; left: 0;"
></iframe>
+ </div>
Processing pre > code tags
This is off by default, so you can turn it on in the options. In that case, you need to install the library used for syntax highlighting as needed. (Currently, only highlight.js is supported.)
npm i highlight.js
# yarn add highlight.js
processer(content, { code: { enabled: true } });
<pre>
<code>
- import { AppProps } from 'next/app'\n\nconst MyApp = ({ Component, pageProps }: AppProps): JSX.Element => {\n return <Component {...pageProps} />\n}\n\nexport default MyApp
+ <span class="hljs-keyword">import</span> { AppProps } from <span class="hljs-string">'next/app'</span>\n\n<span class="hljs-keyword">const</span> MyApp = ({ Component, pageProps }: AppProps): JSX.<span class="hljs-built_in">Element</span> => {\n <span class="hljs-keyword">return</span> <Component {...pageProps} />\n}\n\n<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> MyApp
</code>
</pre>
Also, please bundle the theme CSS for highlighting yourself. Here is an example for your reference.
import "highlight.js/styles/github-dark.css";
import { GetStaticProps, NextPage } from 'next'
import { processer } from "microcms-richedit-processer";
type Props = {
body: string;
};
export const getStaticProps: GetStaticProps<Props> = async () => {
const { contents } = await fetch(
"https://{SERVICE_ID}.microcms.io/api/v1/{ENDPOINT}",
{
headers: {
"X-API-KEY": "{API_KEY}",
},
}
).then((res) => res.json());
return {
props: {
body: await processer(contents.body, {
code: { enabled: true }
}),
},
};
};
const IndexPage: NextPage<Props> = ({ body }) => {
return (
<>
<style global jsx>{`
.content pre > code {
display: block;
padding: 1rem;
}
`}</style>
<div className="content" dangerouslySetInnerHTML={{ __html: body }} />
</>
)
}
Default Behavior of createTableOfContents
I'll introduce the behavior of createTableOfContents, which creates a table of contents list.
The created list follows the same format as the table of contents list introduced on the official microCMS blog.
Source HTML data
<h1 id="h98a35185af">What is Lorem Ipsum?</h1>
<p>
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
</p>
<h2 id="hf76e6834d0">Where does it come from?</h2>
<p>Contrary to popular belief, Lorem Ipsum is not simply random text.</p>
<h3 id="h1c03416cd7">Why do we use it?</h3>
<p>
It is a long established fact that a reader will be distracted by the readable
content of a page when looking at its layout.
</p>
<h4 id="rdAK6TEAQqx">Where can I get some?</h4>
Generated list
[
{ id: "h98a35185af", text: "What is Lorem Ipsum?", name: "h1" },
{ id: "hf76e6834d0", text: "Where does it come from?", name: "h2" },
{ id: "h1c03416cd7", text: "Why do we use it?", name: "h3" },
];
Behavior of createTableOfContents with Options
Changing the heading tags used for the table of contents
createTableOfContents(content, { tags: "h2, h4" });
[
{ id: "hf76e6834d0", text: "Where does it come from?", name: "h2" },
{ id: "rdAK6TEAQqx", text: "Where can I get some?", name: "h4" },
];
Disabling the name key
createTableOfContents(content, { dataForName: false });
[
{ id: "h98a35185af", text: "What is Lorem Ipsum?" },
{ id: "hf76e6834d0", text: "Where does it come from?" },
{ id: "h1c03416cd7", text: "Why do we use it?" },
];
Currently, this option only supports tagName and false, but I plan to add more options if microCMS updates its heading tags with more semantic features in the future.
Conclusion
Processing HTML data is often a surprisingly tedious task when building Jamstack sites. By abstracting these details into a library, I believe it makes the source code much cleaner!
Please give it a try!
URLs
Side note: a small regret
The name of the library I created this time is
microcms-richedit-processer
However, I realized that semantically it might be "processor" instead of "processer"...
But by the time I noticed, it was already too late. Well, the meaning gets across, so I guess it's fine.
Discussion