호진방 블로그
경험

Next.js 블로그 만들기 Ver.1

# Next.js + MDX 블로그 개발기 1탄

2024년 06월 24일

블로그를 직접 만드는 이유

tistory, velog, naver, notion 등 블로그를 위한 플랫폼이 매우 많다. 나 또한 velog에서 notion을 걸쳐 블로그를 운영중이였는데, 쉬우면서도 강력한 기능을 가진 이러한 플랫폼들 덕분에 간단하게 글을 작성하고, 매우 편하게 블로그를 운영 할 수 있었던것 같다. 하지만 해당 플랫폼들을 통한 블로그 운영은 큰 단점이 있었다. 바로 디자인 커스텀이 불가능하다는것이다.


아무리 남들보다 차별화된 블로그를 갖고 싶어도, 정해진 템플릿안에서 글을 작성하기 때문에 개성있는 블로그를 만들기란 쉽지 않았고, 아무리 정성들여 글을 작성해도 애초에 모든 글들이 플랫폼 데이터베이스에 종속 되어 있기 때문에 나만의 사이트에서 쓰여진 나의 글이라는 인식이 존재하지 않았다.

처음 블로그를 시작했을때는 내가 겪은 경험을 남들에게 알려주는 것을 목표로 잡고 지식 공유의 글을 작성했지만, 이러한 인식 때문일까 남들이 보기에는 시중에 널린 글이라고 생각될 것 같아서 글 쓰는것도 귀찮아지고, 단순히 TIL 정도 쓰기에 그쳤던것 같다.

결국 나만의 도메인에서 내가 직접 만든 블로그를 서비스 해보자는 목표를 가지게 되었다. 이는 곧 내가 만든 작업물임과 동시에, 나만의 개성을 뽐내고, 내가 겪은 경험을 남들에게 공유하고 같이 성장하자는 블로그의 진짜 의의에 부합 할 것이다.


기능 정리

목표가 정해졌으니, 기획이 나와야한다. 나만의 플레이그라운드 느낌으로 써보고 싶은 기능 및 기술 스택들이 정말 많지만, 일단 블로그가 돌아가는게 우선이기 때문에 본격적으로 개발하기전 Phase1, Phase2로 우선순위를 나눠 기능을 정리해봤다.

필수 기능 Phase 1

❗ 사실 여기까지만 개발해도 어느정도의 블로그 운영은 가능 할 것이다. 때문에 해당 필수 기능을 phase 1 목표로 잡고 개발이 진행되도록 구현했다.

부가 기능 Phase 2

❗ Phase 2에서는 내가 그동안 써보고 싶었던, 기능 및 기술 스택을 경험하는 것을 목표로 잡았다. 협업프로젝트에서는 여러 이유로 도입하기 꺼려졌던 기술들을 적극적으로 도입함으로써 다양한 기술 스택들을 경험해보고, 블로그 이전의, 나만의 개발 playground 공간으로 사용하기로 했다


기술 스택 정하기

Next

-> 블로그 서비스다 보니 검색엔진에 노출되기 위해서 SEO설정이 필수로 들어가야 했고, 블로그 포스팅은 MDX 파일을 통해 렌더링 되기 때문에 Next/mdx, gray-matter 등 Next 특화적인 라이브러리의 도움을 받기 위해 Next를 선정했다. 또한 기존 개발해왔던 Next Page 라우터 방식이 아닌 App 라우터를 도입해봄으로써, Next App 라우터에 대한 이해도 역시 높일 수 있을것이다.

Tailwind

-> 나에게 가장 익숙한 css 라이브러리이다. 클래스 형식으로 빠르고 간단하게 클래스를 적용할 수 있고, Theme 기능을 지원하기 때문에 폰트나 색상 등 디자인적 요소의 전역적인 수정이 매우 간편하다.

MDX

-> 기존 노션이나 벨로그에서 포스팅한 글들을, 현재 블로그로 마이그레이션 하기 위해 기존에 쓰여진 마크다운 형식의 글을 그대로 긁어와서 mdx 파일의 메타 정보와 본문 내용을 파싱하여 게시글을 보여줄것이다.

Vercel

-> Next.js는 vercel에서 만든 프레임워크인만큼 호환성이 매우 높다. Phase 2 기능 중 하나인 CI/CD 또한 github repository 연동을 통해 간단하게 지원해주며, 도메인 연결 작업이 간편하다.

MDX 렌더링

MDX 파일을 HTML로 변환하고 h2, callout, code 등 각 컴포넌트에 적용한 스타일로 렌더링 하는 과정을 따로 빼서 포스팅할까 했는데, 생각보다 구현이 쉬워 해당 포스팅 글에 작성하기로 했다.

라이브러리 설치 및 next.config 설정

npm install @next/mdx @mdx-js/loader @mdx-js/react

NextJS의 기본적인 MDX 플러그인들을 설치 후 MDX 공식 문서에서 제공하는 Next 연결작업과 구글을 통해 자료조사 한 코드를 참조하여 작성했다. mdx 확장자는 별도의 export문 없이도 하나의 mdx컴포넌트로 default export된다. 아래 next.config.js 설정을 통해 간단하게 MDX파일을 HTML 컴포넌로로 변환 가능해진다.

next.config.js
//MDX 설정 및 라이브러리 연결
const withMDX = createMDX({
  //remarkGfm: GitHub Markdown을 지원
  //remarkFrontmatter: Frontmatter 구문을 파싱
  //remarkMdxFrontmatter: Frontmatter 데이터를 MDX 컴포넌트의 메타데이터로 사용
  //rehypePrettyCode: 코드 블럭을 예쁘게 렌더링
  options: {
    remarkPlugins: [remarkGfm, remarkFrontmatter, [remarkMdxFrontmatter, { name: 'metadata' }]],
    rehypePlugins: [rehypePrettyCode],
  },
});
 
export default withMDX({
  //mdx -> html 변환
  pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
  swcMinify: true, //swcMinify: SWC를 사용한 코드 압축 활성화.
  reactStrictMode: true,
  poweredByHeader: false,
  eslint: {
    ignoreDuringBuilds: true,
  },
 
  //id로 넘어올 때 년도 붙어서 redirect 함수
  redirects() {
    const posts = postsData.posts;
    const redirectPosts = posts.map((post) => ({
      source: `/${post.id}`, // "id": "test1",
      destination: `/${post.tag.split('.')[0]}/${post.id}`, // "tag": "study",
      permanent: true,
    }));
 
    return [...redirectPosts];
  },
});

createMDX 함수를 사용하여 MDX 설정을 만든다. 여기서 Remark 플러그인과 Rehype 플러그인을 설정한다.

redirects 함수를 사용하여 특정 URL 패턴에 따라 리다이렉션을 설정. postsData에서 가져온 데이터를 기반으로 리다이렉션 설정을 생성한다.

즉, /implement-dijkstra 로 이동 할 시 experience/implement-dijkstra 주소로 리다이렉션 되어, 해당 글 Id뿐만 아니라 태그도 함께 보여주어 해당 글의 태그 정보 또한 제공 가능하다.


스타일링은 어떻게 처리 했을까?

mdx-components
import type { MDXComponents } from 'mdx/types';
 
import { Blockquote as blockquote } from '@/components/ui/blockquote';
import { Code as code } from '@/components/ui/code';
import { H1 as h1 } from '@/components/ui/h1';
import { Image } from '@/components/ui/image';
import { P as p } from '@/components/ui/p';
...이외 컴포넌트
 
export function useMDXComponents(components: MDXComponents) {
  return {
    ...components,
    h1,
    p,
    code,
    blockquote,
    Image,
    ...이외 컴포넌트
  };
}

HTML 요소로 변환된 MDX 파일의 컴포넌트들을 MDXComponents 형태로 가져와서 직접 정의한 UI 컴포넌트를 사용하는 설정이다. 즉 MDX 파일에서 기본 HTML 태그 대신 사용자 정의 컴포넌트를 사용하도록 설정하는것이다. 예를 들어, MDX 파일에서 텍스트가 HTML <p> 태그로 변환되면 자동으로 @/components/ui/p에 정의된 P 컴포넌트로 렌더링된다.

P.tsx
'use client';
export function P({ children }) {
  return (
    <p
      className="text-[16px] font-naverNormal my-5 
    [blockquote_&]:font-naverSemi 
    [blockquote_&]:my-2 
    [blockquote_&]:text-[16px] 
    [div_&]:font-naverSemi 
    [div_&]:text-[16px]"
    >
      {children}
    </p>
  );
}

위 P 태그처럼 내가 직접 스타일링이 가능해진다. [blockquote_&], [div_&]로 분기처리 된 모습이다.

노션 글에서 발췌한 내용을 개발자모드로 찍어본 사진이다. p태그는 blockquote 태그로 감싸져 있기 때문에, Tailwind CSS에서 사용되는 JIT(Just-In-Time) 모드의 기능으로 부모선택자에 감싸져 있을 때 스타일을 분기처리 하였다. div는 조금 의아 할 수 있는데

노션의 콜아웃 컴포넌트를 긁어와도, HTML 에서는 직접적인 callout이라는 이름의 태그로 변환하지 않기 때문에, callout태그가 아닌 div로 감싸져 있다.

블로그의 모든 글들의 텍스트들은 div에 감싸진 형태가 아닌 단순히 p태그의 나열로 글을 렌더링하고 있기 때문에 div로 감싸져있다면 자동적으로 callout 컴포넌트라고 판단하고 스타일을 분기처리 해주었다.


글 생성 및 조회 테스트

HTML 변환, 스타일링까지 완료했다면 MDX 파일을 생성하고, 글 조회를 해보자.

현재 블로그에서는 마크다운 작성(글 포스팅) 기능을 지원하지 않기 때문에 직접 APP 라우터 파일트리에 MDX 파일을 추가해야한다. 블로그 내에서 MDX 작성 후 작성된 코드를 파일트리에 저장 하여 자동으로 글 생성까지 할 수 있긴한데, 이미 마크다운 미리보기 서비스는 넘쳐나고, 벨로그나 노션처럼 이미 마크다운 문법으로 쓰여진 글을 긁어올 것이기 때문에 들이는 노력에 비해 굳이? 라는 생각이 들어 글 작성기능은 따로 추가하지 않았다.

App/(post)/tag/postId/page.mdx 파일을 생성하고 아래와 같이 테스트 글 코드를 적어준다.

Page.mdx
import { Callout } from '@/components/ui/callout';
 
//## 테스트글입니다
 
잘나오나? ㅎㅎ
 
> 잘나오나요?
 
<Callout>잘나와?</Callout>

MDX 파일에 작성한대로 일반 텍스트, 인용문, 콜아웃 컴포넌트가 잘 렌더링 되었다.

me
@banhogu
안녕하세요 배움을 나누며 함께 전진하는 1년차 주니어 개발자 방호진입니다.