😁

인생 첫 나만의 블로그를 만든 건에 대하여

blog

2025-04-01

드디어 블로그를 만들었다.

옛날부터 해야지 해야지 했었지만, 그만큼 미루고 미뤄둔 ‘나만의 블로그’ 만들기!
이번에 지원서를 작성하면서, 나만의 강점이 뭘까에 대해서 많이 고민했고 앞으로 어떤 경쟁력을 갖춰야 할까?를 많이 생각했다.

기록은 거짓말을 하지 않는다.

이제껏 내가 기록한 것들, 앞으로 내가 기록할 것들은 모두 나의 큰 자산이 될 것이다. 취직을 할 때엔 자소서 몇천줄 보단 내가 여태 작성한 블로그 글들이 나라는 사람에 대해서 더 자세히 알려줄 것이고, 인생에 있어서 방황을 할 때에도 과거의 내 자신이 쓴 글들이 나를 구해줄 것이다.

그를 위해선 더더욱 나만의 블로그 프로젝트를 완성해야만 했다.

어째서 블로그를 만들었나?

사실 이전에는 벨로그에 글을 주로 썼다. 벨로그는 확실히 가볍게 글을 쓰기엔 좋은 플랫폼이다. 그러나 딱 거기까지다. 가장 큰 불편함은 이미지의 크기 및 스타일 조정이 번거롭다는 것이다. 그리고 블로그 꾸미기가 불가능하다(이게 가장 큰 이유임).

어디선가 그런 글을 본 적이 있다. “프론트엔드 개발자라면, 자기 블로그 정돈 만들어야 하지 않겠는가?”

나도 이 글에 공감하며 나만의 블로그를 만들어보겠노라 다짐을 한지 어언 1년. . . .

사실 그 동안 좀 바빴기도 하고 무엇보다 블로그를 만들 동기가 부족했다. 지금 벨로그로도 충분히 잘 쓰고 있고, 노션에도 프로젝트를 하면서 겪은 트러블 슈팅글을 작성해두고 있었기 때문에 ‘기록만 잘 해두면 되지~’ 라는 생각으로 지내고 있었다.

그러나 25년이 밝아왔고 나는 슬슬 취준에 발을 들였다. 취준을 위해서는 포트폴리오를 만들어야 했는데, 트러블 슈팅 글이 여기 저기에 분산되어 있었다. 프로젝트 A의 트러블 슈팅 글은 프로젝트 A 노션에 있었고, 프로젝트 B의 글은 B 노션에 작성되어 있었다. 따라서 나는 해당 글들을 모두 복사해서 내 노션에 붙여넣기 한 형태로 관리하고 있었다.

위와 같은 형태이다 보니, 원본 글이 업데이트 되면 내가 손수 내 노션에 있는 복사본도 업데이트를 해줘야 했다. 사실 이건 엄청나게 큰 문제가 아니었다. 가장 큰 문제는 ‘블로그의 정체성’이었다.

내 벨로그(Velog)는 알고리즘 + 일상 회고록으로 사용되고 있었다. 그러나 트러블 슈팅과 같이 기술 관련된 글도 어딘가에 저장을 해야 하는데, 나는 노션, 벨로그 둘 다 내 맘에 썩 들지 않았다. 벨로그는 위에서 말했듯이 자유로운 커스터마이징이 불가능하고, 노션은 글들을 관리하기엔 뛰어나지만 그 글들을 보여주는 것에 있어서는 너무 ‘대중적’이었다.

다른 사람들도 벨로그, 노션, 티스토리, 미디엄 등 다양한 플랫폼을 이용하여 자신만의 이야기를 기록해 나가고 있을 것이다. 내가 면접관이라면 좀 지겨울 수 있겠다. 라는 생각이 들었다.

노션, 벨로그, 티스토리 . . . 맨날 똑같네 어디 새로운 거 없나?

흑백요리사에서 나폴리 맛피아가 패자부활전에서 많은 자극적인 음식들 속에서 디저트를 내놓아 살아남고 당당히 우승을 차지한 것처럼, 나는 면접관들에게 작지만 신선함을 주고 싶었기에 ‘나만의 블로그’ 프로젝트를 추진하게 되었다.

뭐로 만들건데?

단연코 NextJS였다. NextJS가 ReactJS 프레임워크로서 점점 프론트개발자의 기본 소양 중 하나로 자리 잡고 있으며, SSR, SSG, ISR, Server Component, App router . . . 등등 배울 것이 참 많은 나에게는 도전 투성이인 프레임워크였기에 NextJS를 선택했다.

블로그가 사실 NextJS의 모든 기능들을 활용하려면 정말 볼륨이 커져야 한다. 그러나 나는 그럴 역량이 없을 뿐더러, 지금 당장 글을 작성할 블로그가 필요했다. 그래서 최대한 작은 볼륨을 지향하며 차차 기능들을 추가해 나가자! 라는 생각으로 일단 CRN 커맨드를 눌러보았다.

스타일 라이브러리도 기존에 사용했던 스타일드 컴포넌트 대신에 테일윈드를 사용하기로 했다. 요즘 매우 핫하기도 하고 한번쯤은 사용하고 싶었다. 그리고 나중에 테일윈드로 프로젝트를 하게 됐을 때 너무 헤매지 않도록 지금 먼저 매를 맞아두자. 라는 생각이었다.

그런데 최근(25/01/22)에 테일윈드 4.0이 출시했다.

이왕 할 거 최신 버젼을 사용해보자! 라는 야욕을 갖고 테일윈드 4.0으로 프로젝트를 시작하게 됐다.(이러질 말았어야 했는데. . . )

그래서 이제 뭘 하면 되지…?

막막했다. 다른 사람들은 잘만 만들던데, 나 혼자 낙동강 오리알 처럼 둥둥 떠다니며 무엇을 해야할지 갈피를 잡지 못했다. 우선 인터넷에 ‘NextJS 블로그 만들기’를 검색해보니 정말 많은 예시들이 나왔다. 그래서 일단 그 예시들을 따라해 보았다.

무수히 많은 에러들이 나를 반겨줬다.

JUST 복붙은 이래서 위험하다는 것을 다시 한번 깨달았다. 그래서 수많은 삽질을 하며, 많은 시간들을 보낸 결과!! 무얼 해야할지 어느정도 알 수 있었다.

우선 블로그 글을 마크다운으로 작성해서 이를 HTML로 파싱하는 작업이 필요하다. 이를 위해서는 라이브러리의 도움을 받아야 했다.

대표적인 라이브러리 2개를 분석해보았다.

@next/mdx

NextJS에서 공식으로 지원하는 로컬에서의 마크다운 파일을 HTML로 파싱할 수 있는 라이브러리이다. MDX 파일을 프로젝트 내에 그대로 import해서 사용하니, 파일 기반 라우팅이 자동으로 지원되어 별도의 복잡한 설정 없이도 쉽게 쓸 수 있다. 빌드 타임에 MDX 파일이 파싱되어 런타임에서는 파싱 오버헤드 없이 빠른 렌더링을 보장해준다.

next-mdx-remote

서버의 getStaticProps나 getServerProps에서 MDX 콘텐츠를 fetch하거나 로컬 파일을 읽어 처리할 수 있어, CMS나 API 등 외부 소스와의 연동이 용이하다. 그러나 파일 기반 라우팅을 자동으로 제공하지 않기 때문에, MDX 콘텐츠를 props로 전달하는 등의 추가 작업이 필요하다. 거기에 더불어 MDX 파일 내 import/export 문법 지원이 제한적이다. 만약 글들을 외부 소스로부터 받아오는 방식이었다면, 해당 라이브러리를 사용했을 것이다.

위 요소들을 고려하여 나는 설정이 단순하고 파일 기반 라우팅을 자동으로 지원하며, 로컬 MDX 콘텐츠를 활용하는 @next/mdx 라이브러리를 사용하기로 결정했다!

@next/mdx 라이브러리 사용하기

자세한 내용은 공식 문서에서 다뤄주고 있다. (사실 그리 자세하지 않은 것 같다ㅠ)

커맨드로 설치해주고

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

next.config 파일에 추가해주고

next.config.ts
1import createMDX from "@next/mdx";
2
3/** @type {import('next').NextConfig} */
4const nextConfig = {
5 // Configure `pageExtensions` to include markdown and MDX files
6 pageExtensions: ["js", "jsx", "md", "mdx", "ts", "tsx"],
7 // Optionally, add any other Next.js config below
8};
9
10const withMDX = createMDX({
11 // Add markdown plugins here, as desired
12});
13
14// Merge MDX config with Next.js config
15export default withMDX(nextConfig);

src폴더 바로 아래 또는 프로젝트 최상단에 mdx-components.tsx 파일을 만들어줘야 한다. 참고로 @next/mdx는 App Router만 사용해야 한다!

mdx-components.tsx
1import type { MDXComponents } from "mdx/types";
2
3export function useMDXComponents(components: MDXComponents): MDXComponents {
4 return {
5 ...components,
6 };
7}

내 App Router 구조는 다음과 같다.

1src/app
2├── (contents)
3│ ├── layout.tsx
4│ ├── markdownTest
5│ │ └── page.mdx
6│ └── notion
7│ └── page.mdx
8├── about
9│ └── page.tsx
10├── layout.tsx
11├── page.tsx
12├── posts
13│ └── page.tsx
14└── projects
15 └── page.tsx

해당 mdx 파일들을 어떻게 가져왔냐? 위에서도 말했듯이 @next/mdx는 src/app/(contents) 디렉토리에 위치한 MDX 파일들을 직접 import하여 사용할 수 있다.

getPosts
1import { PostData } from "@/types";
2import { readdir } from "fs/promises";
3import path from "path";
4
5export async function getPosts(): Promise<PostData[]> {
6 // MDX 파일들이 위치한 디렉토리
7 const postsDir = path.join(process.cwd(), "src", "app", "(contents)");
8
9 // 각 포스트 디렉토리 조회
10 const slugs = (await readdir(postsDir, { withFileTypes: true })).filter(
11 (dirent) => dirent.isDirectory()
12 );
13
14 // 각 MDX 파일에서 metadata를 추출한 후 PostData 형태로 매핑
15 const posts = await Promise.all(
16 slugs.map(async (dirent) => {
17 const { meta } = await import(
18 `../app/(contents)/${dirent.name}/page.mdx`
19 );
20
21 return {
22 slug: dirent.name,
23 emoji: meta.emoji || "",
24 title: meta.title || "",
25 date: meta.date || meta.publishDate || "",
26 preview: meta.preview || "",
27 tag: meta.tag || "",
28 } as PostData;
29 })
30 );
31
32 posts.sort((a, b) => +new Date(b.date) - +new Date(a.date));
33
34 return posts;
35}

이후에 다음과 같이 사용해주면 된다.

usage
1export default async function Home() {
2 const posts = await getPosts();
3 const slicedPosts = posts.slice(0, 5);
4
5 return (
6 <div className="flex flex-col justify-between items-center gap-[10px] w-full">
7 <Intro />
8 <PostList posts={slicedPosts} />
9 </div>
10 );
11}

이외에도 했던 삽질들이 정말 많다. 그건 다른 포스트에서 다루도록 하겠다. 일단 오늘은 여기까지! 다들 나의 첫 블로그 개설을 축하해줘서 정말 고맙다! Thank you guys~~~~~😍

© castle_bell · All rights reserved