Next.js13과 Contentlayer을 이용해서 개인 블로그 구축할 준비하기

Next.js13과 Contentlayer을 이용하여 MDX파일을 다루는 방법을 정리한 포스트입니다. Next.js를 사용하는 이유는 대부분의 경우 개인 블로그는 정적 컨텐츠이기에 Next.js의 SSG를 사용하면 필요한 페이지를 build time에 미리 만들어서 재사용하므로 페이지 로딩속도가 빨라집니다. 또한 Next.js는 기본적으로 SSR 기반이므로 SEO에 도움을 줍니다. 리액트 기반의 클라이언트 컴포넌트가 필요할때는 코드 상단에 "use client"를 추가하여 CSR 도 지원하므로 풀스택 프레임워크로 성장하고 있습니다.


시작

Next.js에서는 프로젝트를 create-next-app을 이용하여 생성할 것을 추천합니다. 필요한 파일들을 자동으로 설치해주기에 웬만해선 안쓸 이유가 없는 것 같습니다. 터미널 창에

npx create-next-app

을 입력하여 Next.js 프로젝트를 생성해줍니다. 기본 설정 값을 물어보는데 저는 기본 값으로 했습니다. (tailwindcss는 강추👍)

Contentlayer

Next.js 공식문서에서는 MDX파일 다루는 법으로 next-mdx-remotecontentlayer 두 가지를 소개합니다. 저는 그 중에서 contentlayer를 택했습니다.

npm i contentlayer next-contentlayer

Contentlayer 설치가 다 되었으면 contentlayer config를 next.config.js에 추가해줍니다. mdx

// next.config.js
const { withContentlayer } = require('next-contentlayer')
 
/** @type {import('next').NextConfig} */
const nextConfig = { reactStrictMode: true, swcMinify: true }
 
module.exports = withContentlayer(nextConfig)

그리고 프로젝트의 루트에 contentlayer.config.ts라는 파일을 만들어서 아래 코드를 입력해줍니다.

//contentlayer.config.ts
import { defineDocumentType, makeSource } from "contentlayer/source-files";
 
const Post = defineDocumentType(() => ({
  name: "Post",
  filePathPattern: `**/*.mdx`,
  contentType: "mdx",
  fields: {
    title: {
      type: "string",
      description: "The title of the post",
      required: true,
    },
    date: {
      type: "date",
      description: "The date of the post",
      required: true,
    },
  },
  computedFields: {
    url: {
      type: "string",
      resolve: (doc) => `/posts/${doc._raw.flattenedPath}`,
    },
  },
}));
 
export default makeSource({
  contentDirPath: "posts",
  documentTypes: [Post],
});

위 코드는 Post documentType을 만들어 주며, flattenedPath로 url field를 만들어 줍니다. document types의 field에 tag와 description 등을 추가할 수 있습니다.

MDX 파일 생성 및 렌더링

이제 MDX 파일을 만들어 봅시다. 프로젝트의 루트에 posts 폴더를 만든 후 안에 test-01.mdx파일을 만듭니다.

---
title: "How to create Next.js app"
date: 2023-04-06
---

# NextJs로 블로그 만들기
## h2 태그
### h3 태그
#### h4 태그

다음으로 npm run dev로 서버를 엽니다. 콘솔창에 아래와 같이 .contentlayer 폴더에 1개의 문서가 생성되었다는 문구가 나오면 성공입니다.

- event compiled client and server successfully in 1284 ms (20 modules)
Generated 1 documents in .contentlayer
- wait compiling...
- event compiled client and server successfully in 85 ms (20 modules)

위와 같이 .contentlayer 폴더에 제가 만든 test-01.mdx파일이 json 형태로 저장된 것을 볼 수 있습니다.

포스트의 작성 날짜를 format하기 위해 date-fns를 설치해줍니다.

npm i date-fns

그리고 tsconfig.json 파일에 아래 코드를 추가하여 generated files을 .contentlayer 폴더에서 가져올 수 있게 해줍니다.

"paths": {
  "@/*": ["./*"],
  "contentlayer/generated": ["./.contentlayer/generated"]
}

이제 app폴더안에 posts폴더와 그 안에 slug폴더를 만든 후 slug폴더 안에 page.tsx를 생성하여 다음 코드를 추가해줍니다.

// app/posts/[slug]/page.tsx
 
import { allPosts } from "contentlayer/generated";
import { getMDXComponent } from "next-contentlayer/hooks";
import { format, parseISO } from "date-fns";
 
export const generateStaticParams = async () =>
  allPosts.map((post:any) => ({ slug: post._raw.flattenedPath }));
 
export const generateMetadata = ({ params }: any) => {
  const post = allPosts.find(
    (post: any) => post._raw.flattenedPath === params.slug: any
  );
  return { title: post?.title, description: post?.description };
};
 
const PostLayout = ({ params }: { params: { slug: string } }) => {
  const post = allPosts.find((post) => post._raw.flattenedPath === params.slug);
 
  let MDXContent;
 
  if (!post) {
    return <div>404</div>;
  } else {
    MDXContent = getMDXComponent(post!.body.code);
  }
 
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{format(parseISO(post.date), "LLLL d, yyyy")}</p>
      <article>
        <MDXContent />
      </article>
    </div>
  );
};
 
export default PostLayout;

위 코드는 포스트 페이지를 slug parameter를 기준으로 렌더링 시켜줍니다. 자세한 방식은 Next.Js 공식문서의 동적 라우트 부분에서 확인할 수 있습니다.

코드 작성을 마친 후 http://localhost:3000/posts/test-01로 접속하면 만들었던 mdx파일을 가지고 잘 렌더링된 화면을 확인할 수 있습니다.

스타일링

그런데 분명 mdx 태그에는 #을 이용해서 h1~h4 태그를 만들었는데 렌더링된 화면에서는 적용이 되어 있지 않습니다. global.css파일에 가서 아래 코드를 추가해주면

article h1 {
  font-size: 2em;
}
article h2 {
  font-size: 1.5em;
}
article h3 {
  font-size: 1.17em;
}
article h4 {
  font-size: 1em;
}

위와 같이 태그가 적용된 모습을 볼 수 있습니다. 그런데 이런 식으로 모든 태그를 스타일링 직접 하기에는 너무 번거로우기에 많이들 tailwindcss를 사용합니다.

npm i -D @tailwindcss/typography

@tailwindcss/typography 플러그인을 설치하고 tailwindcss.config.ts 폴더의 plugins에 아래 코드를 추가해줍니다

  plugins: [
    require('@tailwindcss/typography'),
  ],

global.css파일에 가셔서 상단에 다음 코드를 추가해줍니다.(Next.js CLI로 프로젝트를 생성할 때 tailwindcss를 기본 옵션으로 설정하셨으면 자동으로 추가되어 있습니다. )

@tailwind base;
@tailwind components;
@tailwind utilities;

그리고 app/posts/[slug]/page.tsx 파일에 ariticle 태그에 prose를 className으로 추가해줍니다.

태그 스타일이 자동으로 잘 적용된 것을 볼 수 있습니다. prose로 적용된 스타일을 변경하시고 싶으시면 tailwindcss 공식 문서에서 확인할 수 있습니다.

추가적으로 포스트안에 포함되어있는 소스코드를 스타일링 하시고 싶으시면 rehype-pretty-code를 이용하시면 됩니다.