When it comes to writing content for the web, you’ll eventually come across Markdown. To safely convert my Markdown articles into HTML and React element for my blog, I used the react-markdown library. However, I found the library's documentation a bit difficult to navigate, so I created a guide on how to use it in combination with react-syntax-highlighter to write articles that feature beautifully embedded code blocks, just like the ones we often see in development blogs.
Markdown is a lightweight markup language that uses plain text syntax to indicate formatting. It is a simple way to write formatted text that can be easily converted to HTML to be used on the web.
Markdown allows you to create headings, lists, emphasis, links, images, and more by using simple symbols and characters. For example, to create a heading, you simply add one or more hash symbols (#) at the beginning of a line, followed by the heading text:
# This is a top-level heading ## This is a second-level heading
When this Markdown is rendered to HTML, it will generate the following markup:
<h1>This is a top-level heading</h1> <h2>This is a second-level heading</h2>
So far, I’ve created blog post in Markdown syntax which are stored as string in a database. But how to actually render it into HTML in my react application? The react-markdown library does the magic!
With react-markdown, you can simply pass your Markdown content as a string to the ReactMarkdown
component, and it will automatically generate the corresponding HTML output.
First install the package with your preferred package manager:
npm i react-markdown
Now, let’s see a basic example:
import ReactMarkdown from 'react-markdown'; function App() { return ( <div className="App"> <ReactMarkdown># Hello, *world*!</ReactMarkdown> </div> ); }
The resulting output will be:
<h1> Hello, <em>world</em>! </h1>
We can also pass the markdown text as children prop to the ReactMarkdown
component. It represents the Markdown text you want to render:
import ReactMarkdown from 'react-markdown'; function App() { const markdown = `# Hello, World! This is a **bold** text. `; return ( <div className="App"> <ReactMarkdown children={markdownText} /> </div> ); }
So ReactMarkdown
generates standard HTML tags for each Markdown element and applies some basic styles. What makes the library really useful though is that you can customize how the elements are rendered.
Step 1: Let’s create our custom components and style it according to our design, so for example:
export const ArticleLink = ({ href, children }: any) => { return ( <a href={href} target="_blank" rel="noreferrer" className="font-lato font-medium text-base text-violet-secondary no-underline decoration-violet-secondary mb-4 sm:text-lg sm:mb-6 " > {children} </a> ); };
Check the documentation to see which props to use for which HTML elements.
Step 2: We want to pass these custom components to the ReactMarkdown
component, so that it knows how to render certain HTML tags.
Let's create a MarkdownRenderer
component, so that we can reuse it wherever we need in our app. It receives the markdownText
as prop and renders the ReactMarkdown
component:
import ReactMarkdown from "react-markdown"; import { ArticleH2 } from "./ArticleH2"; import { ArticleH3 } from "./ArticleH3"; import { ArticleImage } from "./ArticleImage"; import { ArticleLink } from "./ArticleLink"; import { ArticleParagraph } from "./ArticleParagraph"; import { ArticleCode } from "./ArticleCode"; export const MarkdownRenderer = ({ markdownText }: any) => { // eslint-disable-next-line react/no-children-prop return ( <ReactMarkdown children={markdownText} components={{ img: ArticleImage, h2: ArticleH2, h3: ArticleH3, p: ArticleParagraph, a: ArticleLink, }} ></ReactMarkdown> ); };
Let's break the code down:
The ReactMarkdown
component accepts a prop called components
: It takes in an object that maps HTML tag names to custom React components.
So it is basically saying:
Hey, if you ecounter an img element in the Markdown text, render the ArticleImage component, if you encounter any h2, then render ArticleH2 ... and so on.
So the key/property represents HTML equivalents for the things you write with markdown (such as h1
for # heading
) and as the value you pass your custom components.
Step 3: Use the MarkdownRenderer
where you need. In my case I use Next.js and getStaticProps to fetch my markdown content from my API:
import ArticleHeader from "@/components/ArticleHeader"; import Button from "@/components/Button"; import { MarkdownRenderer } from "@/features/markdown/MarkdownRenderer"; import { GetStaticPaths, GetStaticPropsContext } from "next"; const backendURL = "http://localhost:5005"; export default function Article({ article }: any) { return ( <div className="w-full bg-violet-bg"> <div className="mx-auto px-5 max-w-[750px] pb-24 sm:px-9"> <div className="flex flex-row justify-end pt-8"> <Button href="/">Go Back</Button> </div> <ArticleHeader title={article.title} date={article.release_date} keywords={article.keywords} /> <MarkdownRenderer markdownText={article.text}></MarkdownRenderer> </div> </div> ); } export async function getStaticProps(context: GetStaticPropsContext) { const response = await fetch( `${backendURL}/api/articles/${context.params?.id}` ); const data = await response.json(); return { props: { article: data, }, revalidate: 60 * 60, }; } export const getStaticPaths: GetStaticPaths = async () => { const response = await fetch(`${backendURL}/api/articles`); const data = await response.json(); const paths = data.map((item: any) => { return { params: { id: `${item.id}`, title: item.title.split(" ").join("-") }, // we define the params for which next js should build this page at build time }; }); console.log(paths); return { paths, fallback: "blocking", }; };
Et voilà! The page you are reading right now is the result of using the MarkdownRenderer
. Well, almost. Just one thing is missing: the highlighted code.
react-markdown will display code in a very plain format, without all the fancy colors we are used to see in our favorite code editor.
For that, we can use another awesome library called react-syntax-highlighter together with react-markdown.
Step 1: install the package
$ npm install react-syntax-highlighter
Step 2: Let’s create a separate component which will be responsible for rendering syntax-highlighted code blocks. Before we define the component, let’s first import the necessary packages and styles in here.
import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter"; import tsx from "react-syntax-highlighter/dist/cjs/languages/prism/tsx"; import typescript from "react-syntax-highlighter/dist/cjs/languages/prism/typescript"; import { nightOwl } from "react-syntax-highlighter/dist/cjs/styles/prism";
PrismLight
version, which is a lightweight version that only includes the languages and styles that are used in our application. With that we need to also manually import the languages we want to use and highlight.Step 3: Register the specific languages you’ve chosen:
SyntaxHighlighter.registerLanguage("tsx", tsx); SyntaxHighlighter.registerLanguage("typescript", typescript);
Step 4: Now we can define our custom ArticleCode
component that uses the SyntaxHighlighter component to render code blocks with syntax highlighting.
export const ArticleCode = ({ inline, className, children, ...props }: any) => { // Extract the language from the className using a regular expression const match = /language-(\w+)/.exec(className || ""); // Render a code block with syntax highlighting if inline is false return !inline ? ( <SyntaxHighlighter children={String(children).replace(/\n$/, "")} style={nightOwl} customStyle={{ borderRadius: "5px", backgroundColor: "#08090a", marginBottom: "1.25rem", padding: "1rem", }} language={match?.[1] || "typescript"} PreTag="div" {...props} /> ) : ( <code className={` bg-gray-200 rounded-sm font-normal ${className} `} {...props} > {children} </code> ); };
The component takes several props, including:
children
- the code that should be highlightedclassName
prop using the format language-<language>
, where **<language>
**is the name of the language being used (e.g. language-typescript
) ( see https://www.npmjs.com/package/react-markdown?activeTab=readme#appendix-b-components)inline
prop is a boolean that determines whether the code block should be rendered inline or as a blockLet’s break the code down:
className
prop using a regular expression.inline
is false
, we render a SyntaxHighlighter
component. If true
, the component renders a code block without syntax highlighting.SyntaxHighlighter
. Additionaly I used the customStyle
prop to customize the code block to my taste and my design. I also set the default language to typescript.Step 5: Finally, we can pass our ArticleCode
component to our MarkdownRenderer
so that it knows how to render code blocks.
/* eslint-disable react/no-children-prop */ import ReactMarkdown from "react-markdown"; import { ArticleH2 } from "./ArticleH2"; import { ArticleH3 } from "./ArticleH3"; import { ArticleImage } from "./ArticleImage"; import { ArticleLink } from "./ArticleLink"; import { ArticleParagraph } from "./ArticleParagraph"; import { ArticleCode } from "./ArticleCode"; export const MarkdownRenderer = ({ markdownText }: any) => { // eslint-disable-next-line react/no-children-prop return ( <ReactMarkdown children={markdownText} components={{ img: ArticleImage, h2: ArticleH2, h3: ArticleH3, p: ArticleParagraph, a: ArticleLink, code: ArticleCode, }} ></ReactMarkdown> ); };
Done! With these libraries combined, you'll be able to create stunning articles with beautifully embedded code blocks - just like the pros. What I really like about this duo is the way you can customize it to fit the style of your website. So, let's start creating amazing content! :)
react-markdown documentation
react-syntax-highlighter documentation
© 2023 designed & built by Elif Gömleksiz