
React 마크다운 미리보기 적용하기👁️
React 프로젝트에서 글 작성 기능을 구현하면서 처음에는 단순히 textarea만 사용해 글을 입력받도록 만들었다.
하지만 마크다운 문법을 사용하는 글이라면 작성하면서 결과를 바로 확인할 수 있는 미리보기 기능이 있으면 훨씬 편리할 것 같았다.
특히 블로그 글이나 프로젝트 설명처럼 마크다운 문법(##, -, code block 등)을 사용하는 경우
작성자가 문법이 제대로 적용되는지 실시간으로 확인할 수 있어 가독성이 훨씬 좋아진다.
그래서 마크다운 실시간 미리보기 기능을 추가했다.
라이브러리 설치
pnpm add react-markdown remark-gfm remark-breaks
react-markdown
Markdown 문자열을 React 컴포넌트로 변환해주는 라이브러리
remark-gfm
GitHub Flavored Markdown(GFM)을 지원하는 플러그인
아래와 같은 문법이 가능하다.
- 테이블
- 체크박스
- 취소선
- 자동 링크
등...
remark-breaks
Markdown에서 Enter로 입력한 줄바꿈을 <br>로 변환해 일반 텍스트처럼 자연스럽게 줄바꿈이 보이도록 해주는 플러그인
실시간 마크다운 미리보기 적용하기
글 작성 화면에서는 왼쪽은 입력창, 오른쪽은 미리보기 영역으로 구성했다.
textarea에 입력되는 값을 formData의 content로 저장하고 그 값을 ReactMarkdown 컴포넌트에 전달해 실시간으로 렌더링한다.
const [formData, setFormData] = useState({
title: '',
content: '',
thumbnailUrl: '',
techStack: [] as string[],
category: null as string | null,
})
<div className="space-y-2">
<Label htmlFor="content" className="font-semibold">
내용
</Label>
<div className="flex w-full flex-col items-start justify-center gap-2 md:flex-row">
<Textarea
id="content"
placeholder="내용을 자유롭게 입력하세요"
className="min-h-[300px] resize-none leading-relaxed focus-visible:ring-1 md:w-1/2"
value={formData.content}
onChange={(e) =>
setFormData({
...formData,
content: e.target.value,
})
}
/>
<div className="markdown-preview w-full md:w-1/2">
<ReactMarkdown remarkPlugins={[remarkGfm, remarkBreaks]}>
{formData.content}
</ReactMarkdown>
</div>
</div>
</div>

게시글 조회(Read) 화면에서 사용하기
글을 작성할 때뿐만 아니라
등록된 게시글을 조회할 때도 Markdown 렌더링이 필요하다.
<ReactMarkdown remarkPlugins={[remarkGfm, remarkBreaks]}>
{description}
</ReactMarkdown>

GitHub Markdown Style 기반 CSS
.markdown-preview {
line-height: 1.6;
color: #334155;
font-size: 16px;
word-wrap: break-word;
}
.markdown-preview h1,
.markdown-preview h2,
.markdown-preview h3 {
padding-bottom: 0.3em;
border-bottom: 1px solid #e2e8f0;
margin-top: 24px;
margin-bottom: 16px;
font-weight: 700;
color: #0f172a;
}
.markdown-preview h1 {
font-size: 2em;
}
.markdown-preview h2 {
font-size: 1.5em;
}
.markdown-preview h3 {
font-size: 1.25em;
}
/* 링크 스타일 */
.markdown-preview a {
color: #3b82f6;
text-decoration: none;
}
.markdown-preview a:hover {
text-decoration: underline;
}
/* 코드 블록 스타일 */
.markdown-preview code {
padding: 0.2em 0.4em;
margin: 0;
font-size: 85%;
background-color: #f1f5f9;
border-radius: 6px;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.markdown-preview pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f8fafc;
border-radius: 8px;
border: 1px solid #e2e8f0;
margin-bottom: 16px;
}
.markdown-preview pre code {
background-color: transparent;
padding: 0;
border-radius: 0;
}
/* 인용문 (Blockquote) */
.markdown-preview blockquote {
padding: 0 1em;
color: #64748b;
border-left: 0.25em solid #3b82f6;
margin: 16px 0;
}
/* 리스트 */
.markdown-preview ul {
list-style-type: disc !important;
padding-left: 1.5rem !important;
margin-block: 1rem;
}
.markdown-preview ol {
list-style-type: decimal !important;
padding-left: 1.5rem !important;
margin-block: 1rem;
}
.markdown-preview li {
display: list-item;
}
/* 테이블 */
.markdown-preview table {
width: 100%;
border-collapse: collapse;
margin-bottom: 16px;
}
.markdown-preview table th,
.markdown-preview table td {
padding: 8px 13px;
border: 1px solid #e2e8f0;
}
.markdown-preview table tr:nth-child(even) {
background-color: #f8fafc;
}
.markdown-preview hr {
margin-top: 2rem;
margin-bottom: 2rem;
}
.markdown-preview p {
word-break: break-word;
margin-bottom: 1rem;
}
.markdown-preview li p {
display: inline;
white-space: normal;
}
⚠️ CSS 파일은 _app.tsx에서 불러오자
Next.js에서는 글로벌 CSS를 아무 곳에서나 import 하면 빌드 오류가 발생할 수 있다.
개발 중에는 문제가 없어 보이지만
빌드 시 다음과 같은 문제가 생길 수 있다.
Global CSS cannot be imported from files other than your Custom <App>

'✨FRONTEND > 📍React' 카테고리의 다른 글
| UI 컴포넌트 라이브러리 비교(feat. Ant Design, HeadlessUI) (2) | 2025.06.01 |
|---|---|
| 컴파운드 컴포넌트 + Headless UI 톺아보기 (1) | 2025.06.01 |
| React 태그 입력 기능 구현하기 (0) | 2025.03.23 |
| Expo 알림(Notification) 구현하기 (2) | 2025.02.25 |
| Expo 앱 배포하기(웹 링크 + Expo QR 코드) (2) | 2025.02.15 |