tips / nextjs
How to Create a Markdown Blog with Next.js
September 01, 2022
- Install Markdown
- Customize 1: sanitize(XSS: Cross-Site Scripting)
- What is HTML sanitization?
- Step 1: Install isomorphic-dompurify
- Step 2: Add to post detail file
- Customize 2: Syntax Highlighting(Prism.js)
- Step 1: Install prismjs
- Step 2: Add to post detail file
- Customize 3: Table of Contents
- Example: Add to post detail file
- Customize 4: html tags
- Example: link
- Reference
I created this Blog with Next.js using Markdown files. It needs to convert from markdown to HTML to display. I’m using marked which is a markdown parser and compiler.
Let’s see how to create and customize Blog with Next.js based on Markdown.
Install Markdown
npm install marked
Customize 1: sanitize(XSS: Cross-Site Scripting)
What is HTML sanitization?
In data sanitization, HTML sanitization is the process of examining an HTML document and producing a new HTML document that preserves only whatever tags are designated "safe" and desired. HTML sanitization can be used to protect against attacks such as cross-site scripting (XSS) by sanitizing any HTML code submitted by a user. Quote: HTML sanitization - Wikipedia
Since marked doesn’t support HTML sanitization, it needs to use a library. In the official doc, marked recommends using DOMPurify as HTML sanitization. Other options are js-xss , sanitize-html , and insane .
I chose DOMPurify which is recommended by marked.
But with Next.js, I needed to use isomorphic-dompurify instead of DOMPurify (reference: DOMPurify.sanitize is not a function ).
Step 1: Install isomorphic-dompurify
npm i isomorphic-dompurify
Step 2: Add to post detail file
import DOMPurify from 'isomorphic-dompurify';
import { marked } from 'marked';
const Post = ({ post }) => {
const dirty = marked(post);
return (
<article dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(dirty) }} />
);
}
Customize 2: Syntax Highlighting(Prism.js)
I added Prism.js to highlight source code in the article.
And also using The parse function of marked.
Step 1: Install prismjs
npm i prismjs
Step 2: Add to post detail file
import Prism from 'prismjs';
import { marked } from 'marked';
const Post = ({ post }) => {
const renderer = new marked.Renderer();
// Set options
marked.setOptions({
renderer,
highlight: function (code, lang) {
if (Prism.languages[lang]) {
return Prism.highlight(code, Prism.languages[lang], lang)
} else {
return code
}
}
});
return (
<article dangerouslySetInnerHTML={{ __html: marked.parse(post) }} />
);
}
Customize 3: Table of Contents
Using lexer
on marked, you can recieve the token array to parser.
marked.lexer(post)
Here is the sample of token array.
[
{
type: 'heading',
raw: '## Headline Text',
depth: 2,
text: 'Headline Text',
tokens: Array(1)
},
{
type: 'paragraph',
raw: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod te'
},
{
type: 'heading',
raw: '### Headline Text',
depth: 3,
text: 'Headline Text',
tokens: Array(1)
},
{
type: 'paragraph',
raw: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod te'
},
{
type: 'paragraph',
raw: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod te'
},
]
Filter to token array, get the headline ( heading
), and format data using map()
method.
You can get the level of heading like h2, h3 with depth
. In my blog, I added data-*
attributes with depth
and styled with css.
Example: Add to post detail file
import { marked } from 'marked';
const Post = ({ post }) => {
let toc;
// トークン配列を取得
const tokens = marked.lexer(post);
// フィルターをかけて見出しのみを取得
const headings = tokens.filter(token => token.type === "heading");
// map()で整える
toc = headings.map((heading: any, index: any) => {
let target = heading.text.replace(/ /g, "-").replace(".", "").toLowerCase()
return (
<li key={index} data-depth={heading.depth}>
<Link href={`#${target}`}>
<a>{heading.text}</a>
</Link>
</li>
)
});
return (
<ul>{toc}</ul>
);
}
Customize 4: html tags
You can customize which HTML tag should be output using renderer
.
Example tags you can customize
- code(string code, string infostring, boolean escaped)
- list(string body, boolean ordered, number start)
- paragraph(string text)
- link(string href, string title, string text)
- image(string href, string title, string text) など
Please check other options on the official doc .
Example: link
import { marked } from 'marked';
const Post = ({ post }) => {
const renderer = new marked.Renderer();
renderer.link = function (href: string, title: string, text: string) {
return `<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>`
}
marked.setOptions({
renderer,
});
return (
<article dangerouslySetInnerHTML={{ __html: marked.parse(post) }} />
);
}
Reference
- Better solution for TOC · Issue #1489 · markedjs/marked · GitHub https://github.com/markedjs/marked/issues/1489(2022-8-20)
- Use marked and prism.js to parse markdown and add syntax highlighting in Node.js https://gist.github.com/lightpohl/f7786afa86ff2901ef40b1b1febf14e0(2022-8-23)
- Add support for NextJS Image - bytemeta https://bytemeta.vip/repo/markedjs/marked/issues/2252(2022-8-23)