목차 기능 - 1부 : 게시글 상단 목차 구현

Administrator||조회수 37


목차 기능 추가: 인라인 목차 구현

1. 목차 기능의 필요성

블로그의 게시물 중 기능 추가와 같은 포스팅은 종종 긴 길이와 복잡한 구조를 갖게 되었다. 그래서 글의 전체적인 흐름을 쉽게 파악하고 원하는 정보에 빠르게 접근할 수 있도록 '목차' 기능을 도입하기로 하였다.

이번 포스트에서는 본문 상단에 글의 전체 구조를 보여주는 '인라인 목차' 를 구현하는 과정을 다룬다.

alt text

2. 핵심 구현 전략

목차 기능 구현의 핵심 과제는 두 가지였다.

  1. 데이터 추출: 마크다운 본문에서 제목(h1, h2 등)을 어떻게 추출하여 목차 데이터로 만들 것인가?
  2. 내비게이션: 목차 항목을 클릭했을 때, 어떻게 본문의 해당 제목 위치로 스크롤을 이동시킬 것인가?

이를 해결하기 위해 react-markdown의 플러그인 생태계를 활용하는 전략을 선택했다.

3. 구현 과정

3.1. 제목 태그에 id 부여하기: rehype-slug

내비게이션을 위해서는 각 제목 태그에 고유한 id 속성이 필요하다. rehype-slugreact-markdown의 rehype 플러그인으로, 제목 텍스트를 기반으로 자동으로 id를 생성하고 HTML 태그에 삽입해준다.

pnpm add rehype-slug rehype-autolink-headings

설치 후, MarkdownViewer 컴포넌트의 rehypePlugins 배열에 추가하여 플러그인을 활성화했다.

// /components/MarkdownViewer.tsx import rehypeSlug from 'rehype-slug'; import rehypeAutolinkHeadings from 'rehype-autolink-headings'; // ... <ReactMarkdown rehypePlugins={[ rehypeRaw, rehypeSlug, [rehypeAutolinkHeadings, { behavior: 'append', content: () => (<span></span>) }] ]} // ... >

이 작업으로 모든 h1~h6 태그에 id="생성된-id" 속성이 자동으로 부여되었다.

3.2. 목차 데이터 추출하기: github-slugger

다음으로, 마크다운 원본에서 제목을 추출하고, rehype-slug동일한 규칙으로 id를 생성하여 목차 데이터 배열을 만들어야 했다. rehype-slug가 내부적으로 사용하는 github-slugger 라이브러리를 직접 사용하여 이 문제를 해결했다.

// /utils/toc.ts import Slugger from 'github-slugger'; export interface Heading { id: string; level: number; text: string; } export function generateToc(markdownContent: string): Heading[] { const headings: Heading[] = []; const headingRegex = /^(#{1,6})\s+(.*)/gm; const slugger = new Slugger(); // 코드 블록 및 마크다운 문법 제거 로직 ... let match; while ((match = headingRegex.exec(contentWithoutCodeBlocks)) !== null) { const level = match[1].length; const text = /* ... 텍스트 정제 ... */; const id = slugger.slug(text); // rehype-slug와 동일한 id 생성 headings.push({ id, level, text }); } return headings; }

generateToc 함수를 통해 마크다운 content만으로 [{ id, level, text }] 형태의 목차 데이터를 생성할 수 있게 되었다.

3.3. 인라인 목차 렌더링

마지막으로, 마크다운 본문에 [toc]라는 특정 구문이 있을 때, 그 위치에 목차 UI를 렌더링하도록 MarkdownViewer를 수정했다.

// /components/MarkdownViewer.tsx // ... <ReactMarkdown components={{ p: (props) => { const { node } = props; // p 태그의 내용이 '[toc]'이면 목차 컴포넌트를 렌더링 if (node?.children[0]?.value === '[toc]') { return <TableOfContents headings={headings} activeId="" />; } return <p>{props.children}</p>; }, // ... }} >

react-markdowncomponents prop을 커스터마이징하여, 특정 조건을 만족하는 p 태그를 직접 만든 TableOfContents 컴포넌트로 교체했다. TableOfContents는 전달받은 headings 데이터를 기반으로 계층적인 목록을 렌더링하고, 각 항목 클릭 시 id를 이용해 부드럽게 스크롤하는 기능을 담당한다.

4. 결과

이제 게시물 마크다운 파일의 원하는 위치에 [toc] 혹은 [목차] 를 추가하는 것만으로, 해당 위치에 자동으로 생성된 인라인 목차를 표시할 수 있게 되었다. 이 기능은 독자가 글을 읽기 전 전체 구조를 파악하는 데 도움을 준다.

다음 포스트에서는 이 기반 위에서, 사용자의 스크롤에 실시간으로 반응하여 현재 위치를 알려주는 '따라다니는 목차'를 구현하고, 사용자가 직접 목차를 접고 펼 수 있는 UI/UX 개선 과정을 다룰 예정이다.

Administrator
Written by

Administrator

안녕하세요! Deep Dive! 블로그 제작자 입니다.

댓글을 불러오는 중...