검색...

Next.JS로 블로그를 다시 만들었습니다.

블로그가 개설된 지 2개월 하고도 1주일이 지났습니다. 올릴 글은 많았지만 올리지 않았습니다. 블로그가 개설된 이후 제가 짜 놓은 코드 꼬락서니가 마음에 들지 않았고 그래서 글도 쓰기 싫어 방치했습니다. 사실 그것도 있고 시간이 많이 없었는데요. 어떤 기회로 Next-Contentlayer를 사용하는 프로젝트를 할 기회가 있어서 그 프로젝트로 어느 정도 감을 잡은 후에 제 블로그를 갈아엎어야겠다는 계획을 세웠습니다. 그리고 지금 블로그를 완벽하게 완성시켰죠. 어떻게 만드는지 과정을 공유해 볼까 합니다.

당장 이 블로그 소스 궁금하신 분은 github.com/dohun0310/d3h1-Blog에 올려놨으니까 그거 보시면 되겠습니다.


갈아 엎을 시간


Jekyll 기반의 블로그는 엉망이었습니다. SCSS 파일을 썼는데 한 파일에 블로그의 모든 스타일링을 다 때려 박았으니 알아보기도 엄청나게 힘들었고 그냥 코드를 엄청나게 이상하게 짰습니다. 그리고 디자이너가 준 디자인을 개발하기 힘들다고 갈아엎은 것도 마음에 안 들었어요. 아니 그냥 다 변명이고 제 마음에 안 들었어요.

그래서 생각했죠. 새로 만들고 다 갈아엎어버려야겠다고요. 그래서 어떻게 만들까 하다가 제가 좋아하는 React로 만들려고 했는데 제가 존경하는 어떤 분께서 Next.JS가 쉽고 좋다고 추천을 해줘서 Next.JS를 사용하기로 했고 Next.JS에 정적 사이트를 쉽게 만들 수 있도록 도와주는 Next-Contentlayer라는 모듈이 있길래 그거 끌고 와서 만들었어요.

이제 뭐 쓸지 다 정했으니까 디자인 받아와야겠죠? 전에 디자인해 주신 분께 모바일 어떻게 만들어야 할지 모르겠다고 말하니까 또 금방 하나 찍어내주셨어요. 멋진 걸로요.

New design


제작 시작


이제 Next.JS로 블로그 제작을 시작해 봅시다. 모두 Node.JSnpm 아니면 yarn 아니면 pnpmcreate-next-app 하나 정도는 있으시죠? 터미널에 아래와 같이 입력하면 프로젝트 파일이 만들어집니다.

create-next-app 프로젝트명

만드셨으면 필요한 모듈들 설치를 해야 합니다. 아래와 같이 진행해 주시면 됩니다.

npm install contentlayer next-contentlayer rehype-highlight rehype-pretty-code shiki next-sitemap @emotion/styled @emotion/css @emotion/react

혹은

yarn add contentlayer next-contentlayer rehype-highlight rehype-pretty-code shiki next-sitemap @emotion/styled @emotion/css @emotion/react

혹은

pnpm add contentlayer next-contentlayer rehype-highlight rehype-pretty-code shiki next-sitemap @emotion/styled @emotion/css @emotion/react

자신이 사용하는 패키지 매니저에 따라서 알아서 해주시면 되고요. 이 모듈들은 블로그 제작을 돕고 CSS in JS를 할 수 있게 해주고 코드 블록을 이쁘게 꾸며주는 플러그인들입니다.

모듈 설치다 했으면 이제 본격적으로 코드 짜봐야겠죠. 그전에 설정부터 먼저 해줍시다.

먼저 프로젝트 최상단에 위치한 next.config.js 파일에 들어가서 수정해 주세요.

//next.config.js

const { withContentlayer } = require("next-contentlayer");

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
};

module.exports = withContentlayer(nextConfig);

먼저 프로젝트 최상단에 위치한 tsconfig.json도 알맞게 수정해주세요.

//tsconfig.json

{
  "compilerOptions": {
    ...
    ],
    "paths": {
      "@/*": ["./*"], "contentlayer/generated": ["./.contentlayer/generated"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".contentlayer/generated", "next-sitemap.config.js"],
  "exclude": ["node_modules"]
}

여기까지 하셨으면 최상단에 contentlayer.config.ts라는 파일 하나 생성해주시고 아래처럼 작성하시면 됩니다.

//contentlayer.config.ts

import { defineDocumentType, makeSource } from "contentlayer/source-files";
import highlight from "rehype-highlight";
import rehypePrettyCode from "rehype-pretty-code";

const Post = defineDocumentType(() => ({
  name: "Post",
  filePathPattern: `**/*.md`,
  contentType: "mdx",
  fields: {
    category: { type: "string", required: true },
    title: { type: "string", required: true },
    date: { type: "date", required: true},
    description: { type: "string", required: true },
  },
  computedFields: {
    url: {
      type: 'string',
      resolve: (doc) => `/${doc._raw.flattenedPath}`,
    },
  },
}))

const contentSource = makeSource({
  contentDirPath: "posts",
  documentTypes: [Post],
  mdx: {
    remarkPlugins: [],
    rehypePlugins: [
      [
        rehypePrettyCode,
        {
          theme: "github-dark"
        },
      ],
      highlight,
    ],
  },
});

export default contentSource;

다른 포털에도 표시 되어야 하니까 최상단에 next-sitemap.config.js 파일 하나 만들어서 아래처럼 작성해야 sitemap이 생성되기 때문에 다른 포털에 표시될 수 있고요.

//next-sitemap.config.js

module.exports = {
  siteUrl: "블로그 주소",
  generateRobotsTxt: true,
}

모듈 설정 끝났으니까 코드 짜봅시다.


마크다운 렌더링 하기


Next.JS가 폴더로 라우팅해서 편하게 우리가 쓸 수 있죠. 저는 도메인 바로 뒤에 아이디가 오는 형식으로 구성하고 싶어서 app/[...slug] 경로에 page.tsx 파일을 만들었어요. 이거 마음에 안 드시면 공식 문서 참고하셔서 다르게 하셔도 되고요.

//app/[...slug]/page.tsx

import { allPosts } from "contentlayer/generated";
import { getMDXComponent } from "next-contentlayer/hooks";
import { notFound } from "next/navigation";
import { format } from "date-fns";

import { StyledPost } from "./style";
import { Footer, Giscus } from "@/components";

export const generateStaticParams = async () => allPosts.map((post) => ({ params: { slug: post._raw.flattenedPath.split('/') }}))

export const generateMetadata = ({ params }: { params: { slug: string[] } }) => {
  const slug = params.slug.join('/');
  const post = allPosts.find((post) => post._raw.flattenedPath === slug)

  if (!post) {
    return {}
  }
  
  return {
    title: post.title,
    description: post.description,
    openGraph: {
      type: "website",
      url: "본인 블로그 주소",
      title: post.title,
      description: post.description,
      siteName: "d3h1 Blog",
    },
    twitter: {
      card: "summary_large_image",
      site: "본인 블로그 주소",
      title: post.title,
      description: post.description,
    },
  }
}

const PostLayout = ({ params }: { params: { slug: string[] } }) => {
  const slug = params.slug.join('/');
  const post = allPosts.find((post) => post._raw.flattenedPath === slug)
  
  if (!post) {
    notFound()
  }

  const Content = getMDXComponent(post.body.code)

  return (
    <StyledPost>
      <div className="post-container">
        <article>
          <h1 className="post-title">{post.title}</h1>
          <time dateTime={post.date}>{format(new Date(post.date), "yyyy년 MM월 dd일")}</time>
          <Content />
          <Giscus />
        </article>
        <Footer />
      </div>
    </StyledPost>
  )
}

export default PostLayout

이제 posts라는 폴더를 프로젝트 최상단에 만들고 그 폴더 안에 example.md 파일을 만들어보세요. 그리고 그 안에는 아래와 같이 작성하세요.

//posts/example.md

---
category: 잡담 //원하는 카테고리
title: 예시 글
date: 2023-07-26 //오늘 날짜
description: Next.JS 프로젝트를 생성하고 처음 쓰는 글
---

아무렇게 쓴 글씨

그리고 터미널에 자신의 패키지 매니저에 맞게 아래와 같은 명령을 입력 후 실행해 보세요.

npm dev

혹은

yarn dev

혹은

pnpm dev

이제 터미널에 뭐라뭐라 뜰겁니다.

warning ../package.json: No license field
$ next dev

이 문구 밑에 뜨는 - ready ~~~를 주목하세요. 거기에 url: 이라고 되어 있는데에 어떤 주소가 있을텐데 그곳을 Ctrl + 클릭하면 브라우저 창이 하나 열릴겁니다. 주소 뒤에 /example을 입력하고 접속해보면 우리가 쓴 글이 잘 렌더링 된 것을 확인 할 수 있죠. 마음에 안드는 부분은 page.tsx@emotion/styled를 바로 가져와서 사용하거나 따로 style.tsx로 빼서 스타일링 해서 사용하시면 됩니다. 나는 CSS in JS가 싫다고 하시면 알아서 하시면 되고요.


글 목록 가져오기


우리 이제 글 표시할 수 있으니까 글 목록을 만들어봐야겠죠. app 폴더 밑에 page.tsx라는 파일이 있을 겁니다. 거기에 우리는 글 목록을 표시해 볼 거예요. 그 파일 열고 아래와 같이 작성하세요.

//app/page.tsx

import { allPosts } from "contentlayer/generated";
import Link from "next/link";
import Image from "next/image";

const Home = () => {
  const Posts = allPosts.sort((a, b) => Number(new Date(b.date)) - Number(new Date(a.date)));

  return (
    {Posts.map((post) => (
      <article key={post._id}>
        <Link href={post.url}>
          <Image src={post.teaser} alt={post.title} width={640} height={360} />
          <p className="post-category">{post.category}</p>
          <h1 className="post-title">{post.title}</h1>
          <p className="post-description">{post.description}</p>
        </Link>
      </article>
    ))}
  );
};

export default Home;

이렇게 page.tsx를 작성하면 아까 우리가 /example을 붙였던 주소에 /example을 지우고 다시 접속해 보면 글 목록이 표시될 겁니다. 쉽죠? 생긴 게 마음에 안 드실 텐데 그것도 나중에 @emotion/styled 쓰시거나 다른 방법으로 스타일링을 하면 됩니다.


마무리


예전에 Jekyll 블로그 만든 건 그냥 코드를 다 빼다 박았는데 이번에는 초보자분들이 알아서 해볼 수 있도록 글을 작성해 봤어요. 제가 저번 주 토요일부터 이걸 만들기 시작했으니까 대충 만드는데 5일 정도 걸렸네요. 이번 블로그는 매우 마음에 듭니다. 제가 코드를 이상하게 짠 거 같지도 않고 훌륭한 분의 도움을 받아 코드가 탄탄하거든요. 제 우분투 세팅 법도 공유하고 싶고 Today at Apple 갔다 온 후기도 써봐야겠습니다. 따라 오시느려고 수고 많으셨습니다. 이제 자신만의 멋진 블로그를 만들어보세요.