Issue with serializers in 11TY + Sanity integration

17 replies
Last updated: Sep 21, 2021
Hello everyone,
I'm still battling with 11TY + Sanity and serializers.

I have an 11ty filter that is working with my serializers.

However, I can't make the serializer for my custom block type "infoText" work:


//from utils/serializers.js


const { h } = require("@sanity/block-content-to-html");


// Learn more on <https://www.sanity.io/docs/guides/introduction-to-portable-text>

module.exports = {
  
types: {
      
cta: ({ node }) => {

        _return_ h(
          
'a',
          
{
            
className:
              
'bg-yellow-500 text-white',
            
href: node.ctaUrl,
          
},
          
node.ctaText,
        
)
      
},
      
infoText: ({ node }) => {

        _return_ h(
          
'p',
          
{
            
className:
              
'bg-blue-500 text-white',
          
},
          
node.bodyInfo.children,
        
)
      
},
    
},
  
}

The node.bodyInfo.children does not work, nor any of the combinations I've tried. My post's data structure is as follows:


{
  "_createdAt": "2021-09-14T11:25:05Z",
  "_id": "89ff5403-326b-4db1-8752-04ea1c85f114",
  "_rev": "7dkOKJtWoyCn0kHUhHzZu7",
  "_type": "post",
  "_updatedAt": "2021-09-20T06:38:14Z",
  "body": [
    {
      "_key": "f84e932860bf",
      "_type": "block",
      "children": [
        {
          "_key": "bd29bce1dda1",
          "_type": "span",
          "marks": [],
          "text": ""
        }
      ],
      "markDefs": [
        {
          "_key": "38aa715c6214",
          "_type": "link",
          "href": "<https://www.fundacionrcoms.com/anti-edad/metodos-eliminar-arrugas/>"
        }
      ],
      "style": "normal"
    },
    {
      "_key": "bf5d17f3da91",
      "_type": "cta",
      "ctaText": "test",
      "ctaUrl": "<https://www.fundacionrcoms.com/>"
    },
    {
      "_key": "595873ddfc54",
      "_type": "block",
      "children": [
        {
          "_key": "ba794ddbef68",
          "_type": "span",
          "marks": [],
          "text": ""
        }
      ],
      "markDefs": [
        {
          "_key": "38aa715c6214",
          "_type": "link",
          "href": "<https://www.fundacionrcoms.com/anti-edad/metodos-eliminar-arrugas/>"
        }
      ],
      "style": "normal"
    },
    {
      "_key": "8acb94638c0c",
      "_type": "infoText",
      "bodyInfo": [
        {
          "_key": "6b6e533e67fd",
          "_type": "block",
          "children": [
            {
              "_key": "3593ad3abdf9",
              "_type": "span",
              "marks": [],
              "text": "test test info"
            }
          ],
          "markDefs": [],
          "style": "normal"
        }
      ]
    },
    .....etc
Any tips? There's not much info on this.
AI Update

I can see you're working with nested Portable Text in a custom block type using @sanity/block-content-to-html in your 11ty project. The issue is that bodyInfo contains an array of Portable Text blocks, not simple text, so you need to handle it differently.

First, it's important to note that @sanity/block-content-to-html is deprecated and the Sanity team recommends migrating to @portabletext/to-html. However, I'll help you solve this with your current setup.

The problem with your infoText serializer is that node.bodyInfo is an array of Portable Text blocks (you can see in your data it has the full block structure with _type: "block", children, etc.), so you can't just access .children directly.

Here's how to fix it with @sanity/block-content-to-html:

const blocksToHtml = require('@sanity/block-content-to-html');
const { h } = require("@sanity/block-content-to-html");

module.exports = {
  types: {
    cta: ({ node }) => {
      return h(
        'a',
        {
          className: 'bg-yellow-500 text-white',
          href: node.ctaUrl,
        },
        node.ctaText,
      )
    },
    infoText: ({ node }) => {
      // Render the nested Portable Text by calling blocksToHtml
      const innerHtml = blocksToHtml({
        blocks: node.bodyInfo,
        // You can pass the same serializers recursively if needed
        serializers: module.exports
      });
      
      return h(
        'div',
        {
          className: 'bg-blue-500 text-white',
        },
        innerHtml
      )
    },
  },
}

Key points:

  1. node.bodyInfo is an array of blocks, not just children. Looking at your data structure, it contains [{ _type: "block", children: [...], markDefs: [], style: "normal" }]

  2. You need to call blocksToHtml() recursively to serialize the nested Portable Text content

  3. I changed the wrapper from <p> to <div> because you're rendering block-level content inside it (paragraphs inside paragraphs isn't valid HTML)

  4. You can pass serializers: module.exports to ensure any custom serializers you've defined also apply to the nested content

Alternative: Extract plain text only

If you only want the plain text without any HTML structure from the nested content:

infoText: ({ node }) => {
  const plainText = node.bodyInfo
    .map(block => 
      block.children
        ?.map(child => child.text)
        .join('')
    )
    .join(' ');
  
  return h(
    'p',
    { className: 'bg-blue-500 text-white' },
    plainText
  )
}

This extracts just the text content ("test test info" in your example) without rendering it as HTML blocks.

Recommendation for the future:

Since @sanity/block-content-to-html is deprecated, consider migrating to @portabletext/to-html when you have time. The new package has better TypeScript support and a more modern API, though the concepts are similar.

Hey Batz! Just to double check, in the example you shared above, does your cta serializer work or are they both broken?
Hi
user M
, the "cta" serializer works as intended, no problem!
At this point I've tried refactoring to this (to see if the problem was that not all Posts have that block), but it also doesn't work (it builds but I don't know if it makes any sense):


infoText: ({ node }) => {

        
const { content = '' } = node.bodyInfo


        _if_ (content !== undefined) {

          _return_ h(
            
'p',
            
{
              
className:
                
'bg-blue-500 text-white',
            
},

            
node.bodyInfo
          
)

          
          
}


          _return_ blocksToHtml.defaultSerializers.types.infoText(node)
      
},

To be more clear, I either get no errors, or "undefined". It builds and the source code displays the


<p class="bg-blue-500 text-white"></p>

...but no content
Got it. I'll spin up an 11ty build and see if I can troubleshoot.
Got it. I'll spin up an 11ty build and see if I can troubleshoot.
Let me make a public repo of it to save you some time, just a sec
Got it. I'll spin up an 11ty build and see if I can troubleshoot.
Thanks!
Thank you for your help, this bit has me anxious! 😅
No problem, serializers can be a beast sometimes!
Hello, the issue has been solved!
It was a problem of me not getting the structured content "architecture".

Once you use


node.bodyInfo.map(({children}) => children.map(child => child.text)).flat().join(''),

It works without a hitch
Hello, the issue has been solved!
It was a problem of me not getting the structured content "architecture".

Once you use


node.bodyInfo.map(({children}) => children.map(child => child.text)).flat().join(''),
However, is there any info on how to make nested HTML elements inside a serializer? I can't find any examples.
user M
To be clear, this is my serializer now:

prosAndCons: ({ node }) =>{


        _return_ h(
          
'ul',
          
{
            
className: 'bg-blue-500 text-white',
          
},
          
node.cons.map( children => '<li>' + children + '</li>'),
        
)
      
},

It outputs:

&lt;li&gt;value1&lt;/li&gt;&lt;li&gt;value2&lt;/li&gt;

And I'm not capable of making it render those &lt;li&gt; tags.

The approach seen here also does not work:


https://katiekodes.com/sanity-11ty-datatable-serializer/
Regarding nested elements, I think reading through the documentation for the Hyperscript repo here could show you some useful examples.
Thanks! I hadn't found that, I'll take a look!
We should probably link to it in our documentation! I'll add it in.

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?