2

I decided to try out Astro by building a web development blog integrated with contentful. I got everything working except for the code blocks. I create an embedded entry for the code block in my content model, which returns the following nodeType:

{
   nodeType: 'embedded-entry-block',
   data: {
      target: {
      metadata: [Object],
      sys: [Object],
      fields: {
      code:
         '```html\n<!DOCTYPE html>\n<html>\n<head>\n<title>My First Astro App</title>\n</head>\n  <body>\n<h1>Hello, World!</h1>\n</body>\n</html>\n```'
        }
      }
    },
    content: []
}

I cannot figure out how to use documentToHtmlString from "@contentful/rich-text-html-renderer" to display the code block in my blog with a themed Prism component.

I passed a custom rendering component for the embedded entry to the documentToHtmlStringoptions, and extracted the lang and HTML code, but then I could not figure out how to return a themed Prism code block. However, I did manage to get it working without styling using the runHighlighterWithAstro function from the @astrojs/prism library.

---
import { documentToHtmlString } from "@contentful/rich-text-html-renderer";
import { BLOCKS } from "@contentful/rich-text-types";
import ContentLayout from "@layouts/ContentLayout.astro";
import BlogPostMeta from "@components/BlogPostMeta.astro";
import { BlogPost, contentfulClient } from "@lib/contentful";
import { Prism } from "@astrojs/prism";
import { runHighlighterWithAstro } from "@astrojs/prism/dist/highlighter";

export async function getStaticPaths() {
  const entries = await contentfulClient.getEntries<BlogPost>({
    content_type: "blogPost",
  });

  function extractCode(str: string): { lang: string; code: string } | null {
    const match = str?.match(/```(\w+)\n([\s\S]+)\n```/);
    if (match) {
      return {
        lang: match[1],
        code: match[2],
      };
    }
    return null;
  }

  const renderOptions = {
    renderNode: {
      [BLOCKS.EMBEDDED_ENTRY]: (node: any) => {
        const { code } = node.data.target.fields;
        const extractedCode = extractCode(code);
        if (extractedCode) {
          const { lang, code } = extractedCode;
          const { classLanguage, html } = runHighlighterWithAstro(lang, code);
          return `<pre class=${classLanguage} set:html=${html}></pre>`;
        }
        return "";
      },
    },
  };

  const pages = entries.items.map((item) => ({
    params: { slug: item.fields.slug },
    props: {
      title: item.fields.title,
      description: item.fields.description,
      content: documentToHtmlString(item.fields.content, renderOptions),
      date: new Date(item.fields.date).toLocaleDateString(),
    },
  }));
  return pages;
}

const { slug } = Astro.params;
if (typeof slug !== "string") {
  throw Error(`Slug should be a string. Received: ${slug}`);
}

const { description, content, title, date } = Astro.props;
---

<ContentLayout title={title} date={date}>
  <BlogPostMeta
    title={title}
    description={description}
    publishDate={date}
    pagePath={`/posts/${slug}`}
    slot='meta'
  />
  <body>
    <h1>{title}</h1>
    <time>{date}</time>
    <article slot='content' set:html={content} />
  </body>
</ContentLayout>

I wanted to use a component, CodeBlockthat contains my styled Prism element. Unfortunately, I may be misunderstanding something about Astro, coming from React land.

---
import { Prism } from "@astrojs/prism";

const { language, code } = Astro.props;
---

<Prism lang={language} code={code} />

<style is:global>...
</style>

0 Answers0