1

i am trying to save my Lexical richText field to a lexicalHTMLField inside a payload CMS Block, but my links are not rendered correctly. All internal links are just shown as <a href="#"></a> I am trying to use the content on my next.js frontend (which is decoupled from payload and queries data over graphql)

In general i am not really sure what is the recommended way to handle this. i dont want to render the lexical content dynamically on the next.js frontend because of SEO reasons.

I tried multiple things including adding custom converters but i cannot get it running. the documentation is not very helpful either unfortunately

I tried to add a converter here, but nothing really works:

import type { Block } from 'payload'

import {
  FixedToolbarFeature,
  HeadingFeature,
  InlineToolbarFeature,
  lexicalEditor,
  lexicalHTMLField
} from '@payloadcms/richtext-lexical'

import { LinkHTMLConverter } from '@payloadcms/richtext-lexical/html'

export const Content: Block = {
  slug: 'content',
  interfaceName: 'ContentBlock',
  fields: [
    {
      name: 'richText',
      type: 'richText',
      localized: true,
      editor: lexicalEditor({
        features: ({ rootFeatures }) => {
          return [
            ...rootFeatures,
            HeadingFeature({ enabledHeadingSizes: ['h2', 'h3', 'h4'] }),
            FixedToolbarFeature(),
            InlineToolbarFeature(),
          ]
        },
      }),
      label: false,
    },
    lexicalHTMLField({
      htmlFieldName: 'richText_html',
      lexicalFieldName: 'richText',
      converters: ({ defaultConverters }) => ({
        ...defaultConverters,
        // tried to add a custom converter here
      }),
    }),
  ],
}

1 Answer 1

0

I had a similar issue to this, concretely needing to look up a field slug on my collection of pages to figure out the target link.

This lexicalHTMLField definition worked for me, modifying only internal links with a relationTo my pages collection:

lexicalHTMLField({
  htmlFieldName: 'html',
  lexicalFieldName: 'content',
  converters: ({ defaultConverters }) => {
    const linkConverter = defaultConverters.link;
    if(typeof linkConverter !== "function") {
      return defaultConverters;
    }

    return {
      ...defaultConverters,
      link: async (opts) => {
        const { fields } = opts.node;
        if(fields.linkType !== "internal" || fields.doc?.relationTo !== "pages") {
          return linkConverter(opts);
        }

        const id = typeof fields.doc.value === "object" ? fields.doc.value.id : fields.doc.value;

        // Lookup page by ID to find slug
        const payload = await getPayload({ config: payloadConfig });
        const page = await payload.findByID({
          collection: "pages",
          id,
          select: { slug: true }
        });

        return linkConverter({
          ...opts,
          node: {
            ...opts.node,
            fields: {
              url: `/${page.slug}`,
              newTab: fields.newTab,
              linkType: "custom",
            },
          },
        });
      },
    };
  },
})

The approach touches as little as possible, returning default behavior unless I find a very specific kind of link, which felt the safest to me.

Essentially, I identify the relevant links and transform them to custom type links with a specific URL, which render correctly as HTML links with that href.

Note the added twist of getting a Local API instance (payload) to make a lookup to another collection in order to get the slug field required these imports:

import payloadConfig from '@payload-config';
import { getPayload } from 'payload';
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.