How to globally configure serializers for @sanity/block-content-to-react?

11 replies
Last updated: Nov 29, 2025
Hello everyone, I'm trying to add the ability to embed youtube videos into my block content, following this tutorial: https://www.sanity.io/guides/portable-text-how-to-add-a-custom-youtube-embed-block
I've set up my schema properly, but I have a question regarding the front-end implementation (
@sanity/block-content-to-react
).

Here , the docs say: "where you want to be able to output this YouTube embed, do the following" - My question is, is there a way to set this up such that I don't need to implement this block of code within every document type that I wish to have the ability to embed videos into? Ideally, this "serialization" would be done at a higher level such that my <BlockContent> widget would look something like this:
<BlockContent blocks={mySchemaType.body} />
, rather than
<BlockContent blocks={mySchemaType.body} serializers={serializers} />

It seems counter-intuitive to have to add this block of code + serializer prop to every single iteration of &lt;BlockContent/&gt; in my project... Maybe I'm not understanding this correctly?
AI Update

You're asking a great question that many developers encounter when working with Portable Text! The good news is that you absolutely can avoid repeating the serializers configuration everywhere.

First, a quick note: the tutorial you're following uses @sanity/block-content-to-react, which is now deprecated. You should use @portabletext/react instead, which is the modern, officially supported library. The API is similar but with some improvements (like using components instead of serializers, and value instead of node).

The Solution: Create a Reusable Component

The best approach is to create your own wrapper component that includes all your custom serializers/components by default. Here's how:

// components/PortableTextRenderer.jsx
import { PortableText } from '@portabletext/react'
import YouTubeEmbed from './YouTubeEmbed'

// Define your components once
const ptComponents = {
  types: {
    youtube: ({ value }) => <YouTubeEmbed url={value.url} />,
    // Add other custom types here
  },
  marks: {
    // Custom marks if needed
  },
  // Other customizations
}

// Create your reusable component
export default function PortableTextRenderer({ value }) {
  return <PortableText value={value} components={ptComponents} />
}

Now throughout your app, you can simply use:

<PortableTextRenderer value={mySchemaType.body} />

This gives you a single place to manage all your Portable Text rendering logic. If you need to add support for another embed type or custom block, you just update this one component.

When You Need Flexibility

If you occasionally need to override or extend the default components for specific use cases, you can make your wrapper more flexible:

export default function PortableTextRenderer({ value, components = {} }) {
  // Merge custom components with defaults
  const mergedComponents = {
    types: { ...ptComponents.types, ...components.types },
    marks: { ...ptComponents.marks, ...components.marks },
    // ... merge other categories
  }
  
  return <PortableText value={value} components={mergedComponents} />
}

This pattern is exactly what you're looking for—it centralizes your configuration while keeping things DRY (Don't Repeat Yourself). The Portable Text documentation covers more advanced customization patterns if you need them.

Remember to migrate to @portabletext/react when you can, as it has better TypeScript support and is actively maintained!

Show original thread
11 replies

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?