Contents
- React에서 Markdown 랜더링하기(react-markdown)
- Front Matter 사용하기(gray-matter)
- 코드블럭(```)의 가독성 높이기(react-syntax-highlighter)
- 코드 열고 닫기
<details>,<summary>사용하기 - 마무리
1. React에서 Markdown 랜더링하기(react-markdown)
React 환경에서 블로그를 만들기로 결정한 뒤, 가장 처음으로 직면한 문제는 Markdown 파일을 어떻게 페이지에 렌더링 할 수 있는가였다.
비슷한 프로젝트가 이미 많았기에 쉽게 react-markdown 라이브러리를 사용하면 해결할 수 있다는 것을 찾아내었다.
react-markdown은 Markdown 파일을 React 컴포넌트로 손쉽게 변환하여 렌더링할 수 있도록 도와주는 라이브러리이다.
적용한 기본 코드
import { useEffect, useState } from 'react'; import ReactMarkdown from 'react-markdown'; export default function Blog() { const [markdown, setMarkdown] = useState(''); useEffect(() => { // Markdown 파일을 가져와 상태에 저장 fetch('./intro.md') .then((response) => response.text()) .then((text) => setMarkdown(text)); }, []); } return ( <div> <ReactMarkdown>{markdown}</ReactMarkdown> </div> );
2. Front Matter 사용하기(gray-matter)
블로그 개발을 진행하면서 '게시글 상세 페이지가 아니라 게시글 하나의 제목, 혹은 여러 게시글의 제목들만 가져오고 싶다면 어떻게 하지?' 고민했다.
이러한 고민의 배경은 Welcome Page에서 가장 최근에 쓴 글 5개를 뽑아서 보여주고 싶었기 때문이다.
처음에는 remark 라이브러리를 사용해 가장 처음에 있는 h1 태그를 추출하는 방식을 시도했다.
초기 코드
import { remark } from 'remark'; export default function extractFirstH1(markdown) { // remark parse를 통해 Markdown 파싱 const tree = remark().parse(markdown); // 트리의 노드들 중 첫 번째 H1 헤딩을 찾는다 for (const node of tree.children) { if (node.type === 'heading' && node.depth === 1) { return node.children .filter((child) => child.type === 'text') // 텍스트 노드만 추출 .map((child) => child.value) // 텍스트 값만 추출 .join(''); } } return ''; // H1 헤딩이 없는 경우 빈 문자열 반환 }
그러나 게시글 목록에서 모든 게시글의 작성일자를 가져와야 했고, 게시글 상세 페이지에서 태그를 가져오는 등 Markdown 파일에서 추출해야 할 데이터가 많아지면서 보다 효율적인 방법을 생각해야했다.
방법을 찾다보니 front matter와 gray-matter 라이브러리로 해결할 수 있었다.
Front Matter : 파일의 맨 앞에 위치한 YAML 형식의 메타데이터 블록
YAML : "YAML Ain't Markup Language"의 약자로, 주로 설정 파일이나 데이터 교환 형식으로 사용
--- title: 글 제목 date: 2024-06-21 tags: ['example', 'markdown'] --- <h1>Hello World!</h1>
gray-matter : Front Matter를 추출해서 Javascript 객체로 반환해주는 라이브러리
gray-matter로 변환한 값
{ content : 'Hello World!', data: { title: '글 제목', date: '2024-06-21' tags: ['example', 'markdown'] } }
gray-matter 라이브러리의 matter()를 이용하는데 렌더링이 안되는 이슈가 발생했다.
gray-matter 라이브러리 사용 시 'Uncaught ReferenceError: Buffer is not defined' 오류였는데, 원인은 Webpack5에서 Node.js 핵심 모듈을 지원하지 않는 것이었다.
이를 해결하기 위해 Buffer를 직접 설치하고 루트 컴포넌트에서 추가하는 방식을 채택했다.
Buffer를 루트 컴포넌트에서 직접 추가한 코드
import { useEffect, useState } from 'react'; import ReactMarkdown from 'react-markdown'; import matter from 'gray-matter'; export default function Blog() { const [markdown, setMarkdown] = useState(''); const [frontmatter, setFrontMatter] = useState({}); useEffect(() => { fetch('./intro.md') .then((response) => response.text()) .then((text) => { const { content, data: frontmatter } = matter(text); setMarkdown(content); setFrontMatter(frontmatter); }); }, []); } return ( <div> <h1>{frontmatter.title}</h1> <ReactMarkdown>{markdown}</ReactMarkdown> </div> );
3. 코드블럭(```)의 가독성 높이기(react-syntax-highlighter)
Markdown 파일에서 작성한 코드블럭을 렌더링했을 때, 기존에 우리가 웹에 접하던 코드블럭처럼 다양한 프로그래밍 언어에 대한 문법 강조를 적용하고 싶다면 react-syntax-highligher 라이브러리를 사용해야한다.
나는 기존에 Visual Studio Code에서 사용하던 테마와 동일하게 vscDarkPlus 스타일을 적용했다.
react-syntax-highlighter 적용
import React from 'react'; import ReactMarkdown from 'react-markdown'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; export default function MarkdownRenderer({ markdown }) { return ( <div> <ReactMarkdown {% raw %} components={{ code({ node, inline, className, children, ...props }) { const match = /language-(\w+)/.exec(className || ''); return !inline && match ? ( <SyntaxHighlighter style={vscDarkPlus} language={match[1]} PreTag="div" {...props} > {String(children).replace(/\n$/, '')} </SyntaxHighlighter> ) : ( <code className={className} {...props}> {children} </code> ); }, }} {% endraw %} > {markdown} </ReactMarkdown> </div> ); }
4. 코드 열고 닫기(<details>, <summary>) 사용하기
코드에 대한 글이 많아질 것을 예상하여, 코드 블록을 필요할 때만 볼 수 있도록 <details>와 <summary> 태그를 사용하고자 했다.
Markdown 내에서 HTML 태그를 렌더링하기 위해서 rehype-raw 라이브러리를 사용했다.
이 방식을 통해 코드 블록을 접었다 펼 수 있게 되어, 글의 가독성을 크게 향상시킬 수 있었다.
rehype-raw 적용
import React from 'react'; import ReactMarkdown from 'react-markdown'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; import rehypeRaw from 'rehype-raw'; export default function MarkdownRenderer({ markdown }) { return ( <div> <ReactMarkdown {% raw %} rehypePlugins={[rehypeRaw]} components={{ code({ node, inline, className, children, ...props }) { const match = /language-(\w+)/.exec(className || ''); return !inline && match ? ( <SyntaxHighlighter style={vscDarkPlus} language={match[1]} PreTag="div" {...props} > {String(children).replace(/\n$/, '')} </SyntaxHighlighter> ) : ( <code className={className} {...props}> {children} </code> ); }, }} {% endraw %} > {markdown} </ReactMarkdown> </div> ); }
마무리
Blog 프로젝트는 공부한 React를 적용하기 위해 간단하게 시작한 사이드 프로젝트이며, 필요한 기능들을 하나씩 찾아가며 구현하고 있다.
물론 블로그를 만들기 위한 더 쉬운 방법도 있겠지만, 이렇게 직접 구현해보면서 각 기술의 필요성과 작동 원리를 깊이 이해할 수 있었다.
앞으로의 계획:
- 댓글 기능 추가
- 카테고리 및 태그 시스템 개선
반응형 디자인 적용- 프로젝트 포트폴리오 추가