For the official way, read this documentation.
In my case, I don’t like this official one because it’s too heavy where I just need to display some markdown files on the site.
Weakness of the method given in this note is the content of markdown file isn’t rendered automatically if the file has changes in the dev mode.
As an example, you can check
devboost.app
- Markdown files are stored in
public
folder so that we canfetch()
them just use their paths. For example,public/sample.md
can be access via/sample.md
.
- Everything is put inside a
"use client"
component.
- If you want to display a content in a server component, put this client component in it.
- Using
raw-loader
allows Next.js to read the markdown file.
- Using
react-markdown
to render the markdown file.
- Bonus: using
react-syntax-highlighter
to display codes with syntax highlights.
Want: click the button → show a popup which contains the rendered markdown.
1// Server component
2<ClientComponent />
1// Client
2"use client"
3
4export default ClientComponent() {
5 const [content, setContent] = useState<string | undefined>(undefined)
6
7 const handleClick = async () => {
8 await fetch('/sample.md').then(res => res.text())
9 .then(text => setContent(text))
10 }
11
12 return (
13 <>
14 {content && (
15 <ReactMarkdown components={MarkdownComponents}>
16 {content}
17 </ReactMarkdown>
18 )}
19 </>
20 )
21}
1import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
2import { dracula } from 'react-syntax-highlighter/dist/esm/styles/prism'
3
4export const MarkdownComponents: object = {
5 code({ node, className, ...props }: any) {
6 const hasLang = /language-(\w+)/.exec(className || '')
7 return hasLang ? (
8 <SyntaxHighlighter
9 style={dracula}
10 language={hasLang[1]}
11 PreTag="div"
12 className="codeStyle"
13 showLineNumbers={true}
14 useInlineStyles={true}
15 >
16 {props.children}
17 </SyntaxHighlighter>
18 ) : (
19 <code className={className} {...props} />
20 )
21 }
22}
Purpose: We want to display a site in
src/app/site-name/page.tsx
. Its content is taken from public/site-name.md
.1import PageContent from '../../components/PageContent'
2
3export default async function SiteName() {
4 return (
5 <div>
6 <PageContent filePath={'site-name.md'} />
7 </div>
8 )
9}
1'use client'
2
3import { useEffect, useState } from 'react'
4import ReactMarkdown from 'react-markdown'
5import { MarkdownComponents } from '../libs/helpers'
6
7type PageContentProps = {
8 filePath: string
9}
10
11export default function PageContent(props: PageContentProps) {
12 const [content, setContent] = useState<string | undefined>(undefined)
13 useEffect(() => {
14 const fetchContent = async () => {
15 const data = await fetch(props.filePath).then(res => res.text())
16 setContent(data)
17 }
18 fetchContent().catch(console.error)
19 }, [])
20
21 return (
22 <>
23 {content && <ReactMarkdown components={MarkdownComponents}>{content}</ReactMarkdown>}
24 <>
25 )
26}