logo

Timur Niroomand

github.com/timn00https://www.linkedin.com/in/timur-niroomand

A Developer's Journey-Log

Timur Niroomand

Rendering Text (Part 2): Serialization of Text in NextJS

August 26, 2023

Working Around PortableText to Render Text In the FrontEnd

Next.JS

Why Write A Custom Serializer Component?

Previously, in part 1, one limitation of PortableText was that it couldn't recognize the "code" type. It simply didn't have such type definitions in its library. Unable to display code blocks in my posts, I attempted a work-around where I:

This held up until I needed to do some in-line styling of various text to change fonts, size, and other personalized formatting. Any attempt to use the TailwindCSS classNames or global CSS simply wouldn't work!

This was because PortableText was receiving and rendering everything.

The solution required a new, more granular component wrapper that would allow you to control the styling of individual text objects that came from the backend.

Constructing the Serializer Component

const serializers = {
  types: {
    code: (props: BlockContent) => {
          return <CodeBlock language={props.language} code={props.code} />;
          },
    block: (props: BlockContent) => {
        
  
        const { style, children, markDefs, listItem } = props;
  
        const renderChildren = (children: any[], markDefs: any[]): ReactNode => {
          return children.map((child, index) => {
            if (child._type === 'span' && child.text) {
              if (child.marks && child.marks.length > 0) {
                const mark = markDefs.find(def => def._key === child.marks[0]);
                if (mark && mark._type === 'link') {
                  return <a href={mark.href} className="text-blue-500">{child.text}</a>;
                }
              }
              return child.text;
            }
            return null;
          }).filter(Boolean);
        };
  
...//continuing to conditional if statements below

Let's break down the 'serializers' object. The types object has properties: 'code' and 'block'.

When the serializer is passed a 'code' key, it returns the value of a CodeBlock component that we imported from elsewhere (formats into code blocks). This text is referred to as 'block-level content.'

And if we pass into the serializer object a block of text that has the type 'block'?

So now we move on to the list item's that are destructured from props: listItems.

 const { style, children, markDefs, listItem } = props;

Within the serializer, using conditional if statements, the blog simply checks if the listItem is a 'bullet', 'number', 'h1 heading', and so on. It then simply applies the corresponding jsx tags and tailwind to the currently rendered text:

const serializer = {
  ....
const renderedText = renderChildren(children || [], markDefs || []);

          // Handle list items with granular control over indentation
      if (listItem === 'bullet') {
        return <ul className="font-sans text-lg iphone12:text-sm list-disc 
         mx-8 px-8 py-3"><li>{renderedText}</li></ul>;
      }
      // Handle heading levels with control over padding and margin and fontsize
      if (style === 'h1') {
        return <h1 className="font-sans text-5xl iphone12:text-3xl font- 
        bold mb-3 pb-5 pt-5">{renderedText}</h1>;
      }
     // Handle normal text with custom font
      if (style === 'normal') {
        return <p className="font-sans tracking-normal text-xl 
        iphone12:text-sm whitespace-normal pb-2">{renderedText}</p>;
      }

      return <p>{renderedText || 'Other types of content.'}</p>;
    },
//and so on 
  },
};
export default serializers;

Then to wrap-up and plug everything in:

In the component where the Post is displayed: The render function maps the post's body text, block by block.

{post.body.map((block, idx) => (
                <React.Fragment key={idx}>
                    {renderBlockContent(block)}
                </React.Fragment>
))}

It iterates over the current text block and renderBlockContent passes in the block's 'type' property (block.type) into the serializer to get the final rendered text with our required styles and responsize tailwind CSS.