ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 블로그에 @next-mdx 적용하기
    카테고리 없음 2023. 2. 12. 23:46

    회사를 다니면서 기존에 알고 있는 기술들에 대한 새로운 소식을 익히기가 쉽지 않았다. 특히 next.js 13 버전은 아예 다른 프레임워크라고 느껴질 정도였다. 이미 큰 규모의 회사 프로젝트들은 next.js 13 으로 마이그레이션하는게 보통 쉬운 일이 아니며, 13 버전은 아직 실험적인 기능들이 많았다. 그래서 혼자 블로그를 만들어 본다고 생각하고 next.js 13 에서 어떤 부분들이 바뀌었나 몸소 체험하기로 했다.

     

    @next-mdx

    MDX란 마크다운 파일에서 직접 JSX를 작성할 수 있는 마크다운의 상위 집합이다. 대부분 블로그를 만들어 본 사람(또는 시도라도 해본 사람)이라면 MDX가 무엇인지는 알고 있을 것이다. 

    보통 remark 라이브러리를 사용해서 md(or mdx) 파일에 작성한 markdown 문서를 html로 파싱해서 블로그 글을 작성했었다. 하지만 이제 next.js 13에서 몇 가지 설정만 해주면 손쉽게 mdx 파일을 파싱할 수 있도록 지원을 해주는 것 같아서 바로 사용해 보았다.

    @next-mdx 공식문서 를 보면 설치 및 next.config.js 설정까지는 쉽게 적용할 수 있다. 하지만 next 13에서 나온 '/app' 디렉토리에 아직 익숙하지 않았던 탓인지 코드 레벨부터는 에러를 자주 만났다. 

    (설치 및 config 설정을 마쳤다면) 그러면 @next-mdx 사용 방법을 알아보자.

    import { MDXProvider } from '@mdx-js/react'
    
    // pages/index.tsx
    export default function App({ Component, pageProps }: AppProps) {
      return (
          <MDXProvider>
            <Component {...pageProps} />
          </MDXProvider>
      )
    }

    공식문서를 보면 이렇게 나와 있는데 pages 디렉토리인 것을 보니 이 부분은 13 버전인 현재 내 블로그에 적용 대상이 아니다. 그래서 그대로 app/page.ts (13 버전) 에 적용하면 되겠지? 하고 적용해 보았지만 에러를 만났다.

     

    next.js 13 의 /app 디렉토리에서는 클라이언트 컴포넌트를 구분하기 위해 해당 파일의 맨 위 상단에 'use client'를 꼭 붙혀주어야만 한다. 단순히 클라이언트 컴포넌트를 구분 짓기 위해 13 버전에서 새롭게 추가된 기능으로 보인다. 

    'use client'
    
    import { MDXProvider } from '@mdx-js/react'
    
    // app/page.tsx
    export default function Home() {
      return (
          <MDXProvider>
            <main>
              <Component />
            </main>
          </MDXProvider>
      )
    }

     

     

    다음으로는 서버 컴포넌트에서 md 또는 mdx 파일을 불러와서 HTML로 파싱 후 마크다운이 화면에 적용되는 과정까지 알아보면 끝이다. 그래도 next.js 13 버전을 공부하고 있으니, next.js 12 버전과 어떤 차이점이 있는지도 알아보면 좋을 것 같다.

    // next.js 12 버전
    
    export const getStaticPaths: GetStaticPaths = async () => {
      // /blog 하위에 있는 md or mdx 파일중에 현재 유저가 보고 있는 파일 불러오기
      const paths = getFiles('blog');
    
      return {
        paths,
        fallback: false,
      };
    };
    
    export const getStaticProps: GetStaticProps = async ({ params }) => {
      // md or mdx 파일을 파싱해서 데이터 추출
      const post = await getFileBySlug("blog", params.slug.join("/"));
    
      console.log('postData', postData);
    
      return {
        props: {
          postData,
        },
      };
    };
    
    const Post = ({ postData }) => {
      const { source, frontMatter } = postData;
      
      return (
        <div>
          {frontMatter.title}
          // ... 생략
        </div>
      );
    }

    기존에는 getStaticPaths 와 getStaticProps 를 사용해서 md or mdx 파일의 데이터를 컴포넌트 내부로 가져왔을 것이다. 하지만 13 버전부터는 getStaticProps를 지원하지 않는다.

     

    사실 평소처럼 getStaticProps 로 개발했다가 저 에러를 만나고 당황했다. 그러면 서버단에서 동적 paths 를 어떻게 불러오지? 라는 생각을 했다가 역시나 공식문서에 답이 있었다. 결론부터 적으면 다음과 같다.

    // app/[slug]/page.tsx
    // 주소를 localhost:3000/hello < 라고 가정해보자
    
    async function Post({ params: { slug } }: Params) {
      const params = slug;
      
      console.log(params) // ['hello']
      
      return (
        //... 생략
    }

     

    그러면 서버단에서 원격 데이터들을 컴포넌트 내부로 어떻게 가져올까? 기존에는 getStaticProps 로 가져왔지만 이제는 그냥 비동기 함수로 가져오면 된다.

    // Next.js 12 버전
    export const getStaticProps = async ({ params }) => {
      // params 는 getStaticPaths를 통해 가져온 props
      const mdxSource = await getPostBySlug(category.dev, params.slug);
    
      return {
        props: {
          data: mdxSource
        },
      };
    };
    
    const Post = ({ data }) => {
      const { mdxSource } = data;
      
      return (
        // ...생략
      
    }
    
    
    
    
    // Next.js 13 버전
    const getData = async(slug: string) => {
      const mdxSource = await getPostBySlug(category.dev, slug);
    
      return mdxSource;
    }
    
    const Post = async ({ params }) => {
      const mdxSource = await getData(params.slug);
      
      return (
        // ...생략
      
    }

     

     

    기존에는 getStaticPaths/getStaticProps 를 통해 인위적(?)으로 가져온 느낌이라면 이제는 정말 서버 컴포넌트 그 자체가 된 것 처럼 보인다.

    그리고 /app 디렉토리 내부는 서버 컴포넌트로 동작하기 때문에 fs 또는 path 라이브러리들을 자유롭게 사용할 수 있어서 md or mdx 파일들을 자유롭게 불러올 수가 있었다.

    위 코드에서 getData 함수 내부를 보면 getPostBySlug를 통해 mdx 데이터를 가져왔는데, 이제 이 함수 내부를 살펴보자.

    import path from "path";
    import fs from "fs";
    import { compileMDX } from "next-mdx-remote/rsc";
    
    export const getPostBySlug = async (type: string, slug: string) => {
      const mdxPath = path.join(root, "posts", type, `${slug}.mdx`);
      const source = fs.readFileSync(mdxPath);
    
      const { content, frontmatter } = await compileMDX({
        source: source,
        options: { parseFrontmatter: true },
        compiledSource: "",
      });
    
      return { content, frontmatter };
    };

    compileMDX를 통해 외부에서 mdx 파일을 불러온 후 파싱할 수가 있었다. 공식문서에는 MDXremote를 사용해서 구현하는 방식으로 설명이 되어 있는데, 공통점은 둘 다 서버에서 실행할 때 사용하는 용도이다. 차이점은 사용 방식의 차이가 있다.

    그리고 마지막으로 posts 폴더에 md or mdx 파일을 만들어서 마크다운 형식으로 글을 작성해주면 끝이다!

    처음 블로그를 만들때 md or mdx 파일을 불러와서 html로 변환 후 블로그 페이지를 완성시키는게 힘들었던 기억이 있다. @next-mdx뿐만아니라 블로그를 만들기 위한 기능들이 더 많이 있다.

    그러니까 더이상 남들이 만든 블로그 테마를 가져다가 만들지 않고 본인만의 블로그를 쉽게 만들 수 있는 세상이 온 것 같다!! 블로그 관리하기가 귀찮아서 티스토리로 넘어왔지만 다시 깃헙 블로그로 넘어갈 생각이다.  next.js 의 새로운 업데이트 기능들을 공부하려면 next.js로 블로그 만드는게 최고인 것 같다.

     

     

    댓글

Designed by Tistory.