Next.js 13: format code snippets from Contentful
Date:
Contentful is a convenient way to host article content. For technical articles, it's very common to have code snippets, either inlined or in blocks. Most likely, we want to highlight the code snippets with some syntax highlighter. This article introduces how we can interpret the code snippets from Contentful and highlight them in a Next.js app.
1. Preparation
Let's start from a Next.js example repo with Contentful integration there: cms-contentful. We would like to have a codebase that is able to fetch and render content from Contentful. Simply follow this instruction: Next.js Starter with Contentful. Please do remember to add content mannually to Contentful:
Add at least 1 author entry and 2 post entries into your space. Click on Content in the top navigation bar to do so. Make sure you publish each entry.
Tip: after publishing content on Contentful, need to clear the local cache to refetch the content to get latest update:
rm -rf .next && yarn dev
.
After going through the steps, you should now be able to view pages locally http://localhost:3000/posts/whats-jses-io:
On top of this, let's handle the code snippets styling.
2. Rich Text Rendering
Usually, we put code snippets here and there randomly within an article. Such an article is usually stored in the field type of Rich Text. This field is similar to traditional "What you see is what you get" (wysiwyg) editors. The key difference here is that the Contentful Rich Text Field (RTF) response is returned as pure JSON rather than HTML.
In an React app, Contentful provides an official renderer library rich-text-react-renderer to help developers render the Rich Text content. Contentful provides the Rich Text content in the format of JSON, representing a tree structure with various BLOCKS
and MARKS
, which are defined in repo rich-text-types. The JSON is like:
Then, we call function documentToReactComponents
from rich-text-react-renderer to transform the JSON into React components. By default, a code snippet is a MARK
, which renders to be inlined like "<code>[snippet code]</code>"
. With the prose
classname by Tailwind, it renders like:
3. Style Customization
If we would like to customize the rendered components, documentToReactComponents
provides options
for that purpose. Depending on the node type, custom renderers could be provided like:
Then define the class jses-inline-code
like:
Then the inline code will be rendered to be:
4. Multi-lines Code Styles
In technical articles, we usually have large piece of multi-lines snippets, like:
With the styles above, it will be rendered like:
For multi-lines code snippets, developers may prefer to use syntax highlighter like react-syntax-highlighter. It supports colored rendering of different variables for different languages, showing line numbers and more other features.
However, Contentful does not distinguish the inline code and multi-lines code snippets. They are both in MARK
node type, while the value of multi-lines code is like:
There are line breaks `\n`
in the string. One way to resolve this quickly is to detect line breaks in the string. If there is any line break, render it with react-syntax-highlighter, otherwise render it with simple <code>
block:
This solution does not quite work well because:
Occasionally we would like to render single line of code with react-syntax-highlighter, this approach cannot achieve
We are not able to render the code snippets of different languages.
So up to now, 27 Oct 2023, the only way is like the solution in Contentful Rich Text Rendering: define a custom content modal for snippets and give it a custom renderer.
The basic idea is: instead of putting code snippets directly inside the Rich Text field, embed an Entry, so that front-end is able to distinguish and style it. Here let's do it step by step.
4.1 New model "Snippet"
On Contentful dashboard, create a new model called "Snippet".
Create a Snippet entry with the code you would like to show, and embed it to the article:
4.2 Fetch embedded entries
The embedded entries are not returned by the current GraphQL query. We need to modify the query to cross refer to the entries and fetch the content. The query in file lib/api.ts
is like:
Basically, it fetches the refered entries to store in the response JSON at path content.links.entries
. And __typename
is the node type of the entry, which is snippet
for us here. Here we need it to allow probably more other types of entries in future.
Check the full file content in the demo repo.
4.3 Embedded entries styling
We would like to highlight the code snippets with react-syntax-highlighter, so install it first:
We here use TypeScript by default, so will also need to install the typing library @types/react-syntax-highlighter. Modify the renderer component lib/markdown.tsx
to render the embedded entries:
In the code, we define a custom renderer for BLOCKS.EMBEDDED_ENTRY
. If the __typename
is snippet
, we render the code snippet with the syntax highlighter.
Note: we also add TypeScript types in the codebase. If you are not using TypeScript, simply ignore them. For all type definitions, check the demo repo.
Finally, we achieve what we are expecting:
5. Performance Tuning
As a performance sensitive developer, you will quickly notice that react-syntax-highlighter introduces a gzipped size 269kb to the bundle. It's basically the largest library in my application. To reduce the bundle size, use Light Build instead. The code will be like:
Try to check the bundle size (I use @next/bundle-analyzer), it's reduce by ~250kb !
6. Conclusion
In this article, we style the code snippets from Contentful for 2 cases:
For inline code, render
MARKS.CODE
to<code>...<code>
For multi-lines snippet, create a separate Content Model "Snippet" and render
BLOCKS.EMBEDDED_ENTRY
Please check the demo repo for the working code. Cheers.