HTML Serializer for Rich Text fields

You can customize the HTML output of Rich Text fields by configuring an HTML Serializer function. This allows you to do things like adding custom classes to certain elements or appending an additional HTML element around another.

Before Reading

This page assumes that you have already included the prismic-vue plugin in your project or that you are using the Prismic Vue.js starter. Check out the Integrating with existing project page to learn how to get setup.

Declaring a global HTML Serializer

It may be necessary to declare a global HTML Serializer that will be applied to every
<prismic-rich-text/> component in your Vue app. You would need to do this if you are managing the routing of your Vue app with a Vue Router, because links that point to internal route of your app should be generated as <router-link> components and not as regular <a> elements.

To declare a global HTML Serializer, you just have to create an HTML Serializer function and set it as the htmlSerializer option when stating the usage of the prismic-vue plugin. Next we will show how to do this.

Here we will demonstrate how to declare a global HTML Serializer for your Vue app. Our goal is to replace all the <a> element with <router-link> component for links that point to a Prismic Document. First, install the prismic-dom dependency, we will need it in our HTML Serializer function. In the terminal, run the following command from your project location:

Copy
npm install prismic-dom

Then create a file named html-serializer.js, in which we'll export the HTML Serializer function:

Copy
import prismicDOM from 'prismic-dom';
import linkResolver from 'path/to/the/link-resolver';

const Elements = prismicDOM.RichText.Elements;

export default function (type, element, content, children) {
  // Generate links to Prismic Documents as <router-link> components
  if (type === Elements.hyperlink) {
    let result = '';
    const url = prismicDOM.Link.url(element.data, linkResolver);

    if (element.data.link_type === 'Document') {
      result = `<router-link to="${url}">${content}</router-link>`;
    } else {
      const target = element.data.target ? `target="'${element.data.target}'" rel="noopener"` : '';
      result = `<a href="${url}" ${target}>${content}</a>`;
    }
    return result;
  }

  // If the image is also a link to a Prismic Document, it will return a <router-link> component
  if (type === Elements.image) {
    let result = `<img src="${element.url}" alt="${element.alt || ''}" copyright="${element.copyright || ''}">`;

    if (element.linkTo) {
      const url = prismicDOM.Link.url(element.linkTo, linkResolver);

      if (element.linkTo.link_type === 'Document') {
        result = `<router-link to="${url}">${result}</router-link>`;
      } else {
        const target = element.linkTo.target ? `target="${element.linkTo.target}" rel="noopener"` : '';
        result = `<a href="${url}" ${target}>${result}</a>`;
      }
    }
    const wrapperClassList = [element.label || '', 'block-img'];
    result = `<p class="${wrapperClassList.join(' ')}">${result}</p>`;
    return result;
  }

  // Return null to stick with the default behavior for everything else
  return null;
};

Make sure to have a default case that returns null. This will leave all the other elements untouched.

Finally, set this HTML Serializer function as the htmlSerializer option when stating the usage of the prismic-vue plugin. In the Prismic Vue.js starter this is done in src/main.js

Copy
import Vue from 'vue';
import PrismicVue from 'prismic-vue';
import linkResolver from 'path/to/the/link-resolver';
import htmlSerializer from 'path/to/the/html-serializer';

// ...

Vue.use(PrismicVue, {
  endpoint: window.prismic.endpoint,
  linkResolver: linkResolver,
  htmlSerializer: htmlSerializer
});

// ...

Applying an HTML Serializer on specific Rich Text fields

It's possible to apply an HTML Serializer on one or more specific Rich Text fields. You just have to create an HTML Serializer function and pass it to the optional htmlSerializer prop of the desired <prismic-rich-text/> components. As shown in the example below:

Copy
<template>
  <div>
    <blockquote>
      <prismic-rich-text
        :field="fields.blockquote"
        :htmlSerializer="anotherHtmlSerializer"
      />
    </blockquote>
  </div>
</template>

<script>
import prismicDOM from 'prismic-dom';

const Elements = prismicDOM.RichText.Elements;

const anotherHtmlSerializer = function (type, element, content, children) {
  // Add a class to paragraph elements
  if (type === Elements.paragraph) {
    return `<p class="my-paragraph-quote">${children.join('')}</p>`;
  }

  // Return null to stick with the default behavior for everything else
  return null;
}

export default {
  data () {
    return {
      anotherHtmlSerializer,
      fields: {
        blockquote: null
      }
    };
  },
  methods: {
    // This is an example query, the important part is above.
    getContent () {
      this.$prismic.client.getSingle('example')
        .then((document) => {
          this.fields.blockquote = document.data.blockquote;
        })
    }
  },
  created () {
    this.getContent();
  }
};
</script>

Example with all elements

Here's an example that shows you how to customize all of the available Rich Text elements:

Copy
import prismicDOM from 'prismic-dom';
import linkResolver from 'path/to/the/link-resolver';

const Elements = prismicDOM.RichText.Elements;

export default function (type, element, content, children) {
  // Generate links to Prismic Documents as <router-link> components
  if (type === Elements.hyperlink) {
    let result = '';
    const url = prismicDOM.Link.url(element.data, linkResolver);

    if (element.data.link_type === 'Document') {
      result = `<router-link to="${url}">${content}</router-link>`;
    } else {
      const target = element.data.target ? `target="'${element.data.target}'" rel="noopener"` : '';
      result = `<a href="${url}" ${target}>${content}</a>`;
    }
    return result;
  }
	
  // If the image is also a link to a Prismic Document, it will return a <router-link> component
  if (type === Elements.image) {
    let result = `<img src="${element.url}" alt="${element.alt || ''}" copyright="${element.copyright || ''}">`;

    if (element.linkTo) {
      const url = prismicDOM.Link.url(element.linkTo, linkResolver);

      if (element.linkTo.link_type === 'Document') {
        result = `<router-link to="${url}">${result}</router-link>`;
      } else {
        const target = element.linkTo.target ? `target="${element.linkTo.target}" rel="noopener"` : '';
        result = `<a href="${url}" ${target}>${result}</a>`;
      }
    }
    const wrapperClassList = [element.label || '', 'block-img'];
    result = `<p class="${wrapperClassList.join(' ')}">${result}</p>`;
    return result;
  }

  switch (type) {
    case Elements.heading1:
      return `<h1>${children.join('')}</h1>`;

    case Elements.heading2:
      return `<h2>${children.join('')}</h2>`;

    case Elements.heading3:
      return `<h3>${children.join('')}</h3>`;

    case Elements.heading4:
      return `<h4>${children.join('')}</h4>`;

    case Elements.heading5:
      return `<h5>${children.join('')}</h5>`;

    case Elements.heading6:
      return `<h6>${children.join('')}</h6>`;

    case Elements.paragraph:
      return `<p>${children.join('')}</p>`;

    case Elements.preformatted:
      return `<pre>${children.join('')}</pre>`;

    case Elements.strong:
      return `<strong>${children.join('')}</strong>`;

    case Elements.em:
      return `<em>${children.join('')}</em>`;

    case Elements.listItem:
      return `<li>${children.join('')}</li>`;

    case Elements.oListItem:
      return `<li>${children.join('')}</li>`;

    case Elements.list:
      return `<ul>${children.join('')}</ul>`;

    case Elements.oList:
      return `<ol>${children.join('')}</ol>`;

    case Elements.embed:
      return (`
        <div data-oembed="${element.oembed.embed_url}"
          data-oembed-type="${element.oembed.type}"
          data-oembed-provider="${element.oembed.provider_name}"
        >
          ${element.oembed.html}
        </div>
      `);

    case Elements.label:
      const label = element.data.label ? ` class="${element.data.label}"` : '';
      return `<span ${label}>${children.join('')}</span>`;

    case Elements.span:
      return content ? content.replace(/\n/g, "<br />") : '';

    default:
      return null;
  }
};