블로그가 어떻게 동작하는지 알아보기 - MDX

블로그가 어떻게 동작하는지 알아보기 - MDX

2024년 09월 01일
이번 포스팅에서는 블로그에 사용한 라이브러리와 MDX 파일을 불러오는 방식을 살펴보겠습니다.

나의 니즈 파악하기

  1. 운영비용 0원
  2. 디자인 커스텀 쉽게
  3. SEO 최적화
  4. 페이지 빠른 로딩
  5. 데이터베이스 사용안하기
그럼 이제 자세히 알아보겠습니다

운영비용 0원

이는 Vercel을 이용하면 쉽게 해결할 수 있습니다.

Vercel란?

Vercel은 Serverless 환경을 지원하는 호스팅 플랫폼입니다. 라우트와 서버 측 코드가 Serverless Functions으로 배포되며, 무료 호스팅이 가능합니다. 단, 유료 버전으로 업그레이드할 경우 AWS보다 비쌀 수 있으니 주의가 필요합니다.

디자인 커스터마이징 용이

디자인은 아무래도 가장 중요한 부분입니다. 각 서비스에 맞는 디자인이 필요하지만, 저는 디자인에 능숙하지 않아서 토스 블로그를 레퍼런스로 삼았습니다. 앞으로 추가될 기능들은 별도로 계획하고, 우선 원하는 방향으로 디자인을 구현하고 덧붙일 예정입니다.

SEO 최적화 , 페이지 빠른 로딩

리액트 기반으로 이 두 가지를 충족할 수 있는 프레임워크로는 Gatsby.js , Next.js가 있습니다. 저는 Next.js를 선택했습니다.

선택한 이유

  1. 현 실무에서 사용하는 프레임워크이다.
  2. AppRouter 함수들을 더 학습할 필요가 있습니다.

데이터베이스 사용하지 않기

MDX를 사용하여 파일 기반의 콘텐츠 관리 방식을 선택했습니다. 이렇게 하면 데이터베이스의 복잡함을 피하고, 개발과 유지보수 측면에서 유리하게 콘텐츠를 관리할 수 있습니다.

어떤 라이브러를 사용했을까?

package.json
"dependencies": {
    "@next/third-parties": "^14.2.7",
    "@vercel/analytics": "^1.3.1",
    "next": "14.2.5",
    "prismjs": "^1.29.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1"
  },

"devDependencies": {
    "@mdx-js/loader": "^3.0.1",
    "@mdx-js/react": "^3.0.1",
    "@next/mdx": "^14.2.7",
    "@types/mdx": "^2.0.13",
    "@types/node": "^20.16.2",
    "@types/prismjs": "^1.26.4",
    "@types/react": "^18.3.4",
    "@types/react-dom": "^18.3.0",
    "eslint": "^8.57.0",
    "eslint-config-next": "14.2.5",
    "gray-matter": "^4.0.3",
    "image-size": "^1.1.1",
    "next-mdx-remote": "^5.0.0",
    "sass": "^1.77.8",
    "sharp": "^0.33.5",
    "typescript": "^5.5.4"
}

prismjs

MDX의 코드 문법을 강조해주는 라이브러리입니다.

@mdx

React 애플리케이션에서 MDX 파일을 렌더링할 수 있도록 도와주는 라이브러리로, 이 블로그에서 가장 중요한 역할을 합니다.

next-mdx-remote

MDX 라이브러리를 동적으로 사용할 수 있게 해주는 라이브러리입니다.

gray-matter

MDX 파일을 포함한 마크다운 파일에서 메타데이터와 콘텐츠 내용을 추출하는 데 사용되는 라이브러리입니다.
gray-matter 사용방법
import matter from 'gray-matter';

/* 
MDX
---
title: 블로그는 어떻게 구성되어 있을까
description: 블로그가 어떻게 구성되었는지, 어떤 방식으로 동작하는지를 설명합니다.
date: 2024.8.27
thumbnail: image/thumbnail/20240827.webp
path: how to make
series: 블로그 제작기
---
안녕하세요 


//matter types

const content: string;

const data: {
    [key: string]: any;
}
*/

const { data, content } = matter(fileContents);

gray-matter result

console.log(data)
=>{
  title: '블로그는 어떻게 구성되어 있을까',
  description: '블로그가 어떻게 구성되었는지, 어떤 방식으로 동작하는지를 설명합니다.',
  date: '2024.8.27',
  thumbnail: 'image/thumbnail/20240827.webp',
  path: 'how to make',
  series: '블로그 제작기'
}

console.log(content)
=>
안녕하세요
data는 객체 형태로 반환되며, content는 문자열로 반환됩니다.

image-size

로컬 이미지의 크기를 알려주는 라이브러리로, 이미지 최적화를 위해 사용했습니다.

sass

Sass는 CSS 전처리기로, CSS 기능을 확장하고 작성과 유지 보수를 더 쉽게 만들어줍니다. 제가 애용하는 라이브러리입니다.

어떤 함수를 사용했을까

자세한 코드는 useReadMdx.tsx 확인하실 수 있습니다.
export const useReadMdx = async (pathUrl?: string): Promise<MDX.Metadata[] | MDX.DetailProps> => {
  const markdownDirectory = path.join(process.cwd(), '_markdown');
  const filenames = await fs.readdir(markdownDirectory);

  let current_content = undefined;
  let current_tag: string[] = [];
  let current_series: string | undefined = undefined;

  const markdownMetaData: MDX.Metadata[] = await Promise.all(
    filenames.map(async (filename) => {
      const filePath = path.join(markdownDirectory, filename);
      const meta = await parseMarkdownFile(filePath);
      if (!!pathUrl && normalizePath(pathUrl) === meta.path) {
        const fileContents = await fs.readFile(filePath, 'utf8');
        const { content, data } = matter(fileContents);
        current_content = content;
        current_tag = data.tags;
        current_series = data.series;
      }
      return { ...meta };
    })
  ).then((data) => data.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()));

  if (pathUrl && typeof current_content === 'string') {
    const currentMetaIdx = markdownMetaData.findIndex((data) => data.path === normalizePath(pathUrl)) as number;

    let tagsArray = [];
    for (const tag of current_tag) {
      const resultSearchData = markdownMetaData.filter((data) => data.path !== normalizePath(pathUrl) && data.tags.includes(tag));
      resultSearchData.length && tagsArray.push({ [tag]: resultSearchData });
    }

    return {
      content: current_content,
      meta: markdownMetaData[currentMetaIdx],
      series: !!current_series ? markdownMetaData.filter((data) => data.series === (current_series as string)).reverse() : null,
      tags: !!current_tag.length ? tagsArray : null,
    };
  } else {
    return markdownMetaData;
  }
};

코드 설명

전체적으로 보면 pathUrl 유무에 따라 로직이 달라집니다.

공통

_markdown폴더 안에 있는 파일을 모두 읽어온 후, 전체 파일을 메타데이터 기준으로 최신순으로 정렬합니다.

pathUrl 있을떄

포스팅 상세 페이지를 보기 때문에, 해당 path에 따른 메타데이터, 같은 태그의 게시물, 시리즈 게시물, 해당 게시물의 내용 등을 반환합니다.

pathUrl 없을때

pathUrl이 없다는 것은 메인 화면을 의미하므로, 전체 메타데이터만 반환합니다.

마치면서

이번 포스팅에서는 사용한 라이브러리와 MDX 파일을 불러오는 방법을 알아보았고, 다음 포스팅에서는 Next.js의 함수들을 활용하는 방법을 살펴보겠습니다.