Troubleshooting serializer for text and headings in PortableText component

13 replies
Last updated: May 4, 2022
Hi again, trying to write a serializer and can't figure out how to handle pure text.It looks like pure text is of type "span" from my console.logging, but I can't get my serialier to actually do anything with span.


const serializer = {
  types: {
    figure: ({ value }) => <Image data={value} />,
    span: ({ value }) => <Text fontSize="8em">..</Text>,
    // Headings are also not working ):
    block(props) {
      switch (props.node.style) {
        case "h1":
          return <Text fontSize="8em">..</Text>;
        case "h2":
          return <Text fontSize="8em">..</Text>;
        case "span":
          return (
            <Text fontSize="12em">
              Should span be here instead? Still not working though
            </Text>
          );
      }
    },
  },

const Content = (props) => {
  return <PortableText value={props.value} components={serializer} />;
};
};
Any suggestions?
AI Update

Looking at your code, I can see the issue! In Portable Text, span isn't actually a type you can customize in the types object. Spans are the text content within blocks, and they're handled differently.

Here's what's happening in your Portable Text structure:

  • Blocks (like paragraphs, headings) are handled in types.block
  • Spans are the actual text content inside those blocks
  • Marks (like bold, italic, or custom annotations) are handled in marks

To customize how text renders, you need to target the block type. Your block handler is on the right track, but you need to return something for the default "normal" style too:

const serializer = {
  types: {
    figure: ({ value }) => <Image data={value} />,
    block(props) {
      switch (props.node.style) {
        case "h1":
          return <Text fontSize="8em">{props.children}</Text>;
        case "h2":
          return <Text fontSize="6em">{props.children}</Text>;
        case "normal": // This is the default paragraph style
          return <Text fontSize="2em">{props.children}</Text>;
        default:
          return <Text>{props.children}</Text>;
      }
    },
  },
};

The key things to fix:

  1. Remove the span from types - it's not a valid type to override there
  2. Use props.children in your block handler - this renders the actual text content
  3. Handle the "normal" style - plain paragraphs have style: "normal", not "span"

If you want to customize individual text spans (like making specific words different), you'd use the marks object instead. For example, if you had a custom annotation or wanted to override how bold text renders:

const serializer = {
  types: { /* your block handlers */ },
  marks: {
    strong: ({ children }) => <Text fontWeight="bold">{children}</Text>,
    // custom marks would go here too
  },
};

The ultimate guide for customizing Portable Text has great examples of all the different ways you can customize rendering, including blocks, marks, and annotations!

Show original thread
13 replies
You’re checking against
node.style
in your switch so I think you want
normal
instead of
span
.
is checking against node.style the preffered method? Also having some issues with headings
Tried my hand at
const serializer = {
  types: {
    figure: ({ value }) => <Image data={value} />,
    // Headings are also not working ):
    block(props) {
      switch (props.node.style) {
        case "h1":
          return <Text fontSize="8em">NOT WORKING h1</Text>;
        case "h2":
          return <Text fontSize="8em">NOT WORKING h2</Text>;
        case "normal":
          return <Text fontSize="12em">NOT WORKING TEXT </Text>;
      }
    },
  }
With no success
That’s how it’s done in the package docs , at least.
If sanity does it it's good enough for me. Now I just need to get it to work
Are the headings working or are they not either?
Only my figure component is working, nothing else (nothing else includes the headings)
Using @portabletext/react btw
Oh, sorry. I thought I saw you using serializers and blocks props. I have something to take care of now but will take another look a bit later.
the only docs on how to serializer normal text and headings were for the "old" ways I guess.
Tried updating the code, still got the same issue
import { PortableText } from "@portabletext/react";
import { Text } from "@chakra-ui/react";

import Image from "../components/image";
const components = {
  block: {
    figure: ({ value }) => <Image data={value} />,
    h1: ({children}) => <Text as="h1" fontSize="8em">{children}</Text>,
    h2: ({children}) => <Text as="h2" fontSize="12em">{children} test</Text>,
    normal: ({children}) => <Text fontSize="1em">{children}</Text>,
  },
};

const Content = (props) => {
  return <PortableText value={props.value} components={components} />;
};

export default Content;
Got it working.IMO the PortableText method is way sexier than the block content method, ty sanity
🙏I just needed to figure it out
For anyone stumbling accross this

const components = {
  types: {
    figure: ({ value }) => <Image data={value} />,

  },
  block: {
    h1: ({children}) => <Text as="h1" fontSize="12em">{children}</Text>,
    h2: ({children}) => <Text as="h2" fontSize="4em"> TEST{children}</Text>,
    normal: ({children}) => <Text>{children}</Text>,
  }
};

const Content = (props) => {
  return <PortableText value={props.value} components={components} />;
};

export default Content;
Great job, and that’s nice to hear! Thanks Andreas.

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?