상세 컨텐츠

본문 제목

[개발자 포트폴리오 > AboutMe] 목차 네비게이션 구현 | 마크다운 컴파일, withSlugs, withToc, withTocExport

Project/포트폴리오(NextJS + Notion API)

by yooputer 2025. 6. 9. 16:42

본문

이번 포스팅에서는 마크다운 내용에서 목차를 추출한 후 해당 목차로 이동할 수 있는 네비게이션을 구현해보려 한다. 

 

지난 포스팅

 

[개발자 포트폴리오 > AboutMe] 마크다운 화면에 뿌리기, word highlighting 적용 | MDXRemote, Tailwind

이번 포스팅에서는 저번 포스팅에서 구현한 getAboutMeContent 함수를 사용하여노션페이지에서 조회한 마크다운을 화면에 뿌리는 과정에 대해 정리해보려 한다. 지난 포스팅 AboutMe] 노션 페이지 내

yooputer-devlog.tistory.com


목차 interface, 컴포넌트 생성

interface TocEntry {
  value: string;
  depth: number;
  id?: string;
  children?: Array<TocEntry>;
}

function TableOfContentsLink({ item }: { item: TocEntry }) {
  return (
    <div className="space-y-2">
      <Link
        key={item.id}
        href={`#${item.id}`}
        className={`hover:text-foreground text-muted-foreground block font-medium transition-colors`}
      >
        {item.value}
      </Link>
      {item.children && item.children.length > 0 && (
        <div className="space-y-2 pl-4">
          {item.children
              .map((subItem) => {
                return {
                  ...subItem,
                  value: subItem.value
                }
              })
              .map((subItem: TocEntry) => (
                  <TableOfContentsLink key={subItem.id} item={subItem} />)
              )
          }
        </div>
      )}
    </div>
  );
}

 

마크다운 컴파일

마크다운 문자열에서 목차를 추출하기 위해서는 마크다운을 컴파일하여야 한다. 

import { compile } from '@mdx-js/mdx';
import { getAboutMeContent } from '@/lib/notion';
import rehypeSanitize from 'rehype-sanitize';
import withSlugs from 'rehype-slug';
import withToc from '@stefanprobst/rehype-extract-toc';
import withTocExport from '@stefanprobst/rehype-extract-toc/mdx';

export default async function AboutMe() {
  const { markdown } = await getAboutMeContent();

  const { data } = await compile(markdown, {
    rehypePlugins: [
      withSlugs,
      rehypeSanitize,
      withToc,
      withTocExport,
    ],
  });
  
	...

 

컴포넌트 매핑

그리고 사이드바에서 컴파일한 data 객체를 사용하여 목차 링크 컴포넌트로 매핑 한다 

{/* 왼쪽 사이드바 */}
<div className="hidden md:block">
  <div className="sticky top-16">
    <div className="bg-muted/60 space-y-4 rounded-lg p-6 backdrop-blur-sm">
      <h3 className="text-lg font-semibold">About Me</h3>

      <nav className="space-y-3 text-sm">
        {data?.toc?.map((item) => <TableOfContentsLink key={item.id} item={item} />)}
      </nav>
    </div>
  </div>
</div>

 

이때 마크다운을 렌더링하는 MDXRemote 컴포넌트의 mdxOptions에 withSlugs 플러그인이 추가되어야 

목차 링크를 클릭하였을 때 정상적으로 해당 위치를 이동한다. 

// 기존 코드 수정(withSlugs 추가)
<MDXRemote
  source={markdown}
  options={{
    mdxOptions: {
      remarkPlugins: [remarkGfm],
      rehypePlugins: [withSlugs, rehypeSanitize],
    },
  }}
/>

 

 

 

관련글 더보기