Astro와 MDX로 블로그 만들기
Astro와 MDX를 활용하여 현대적인 블로그를 구축하는 과정을 소개합니다
Astro와 MDX로 블로그 만들기
안녕하세요! 오늘은 Astro와 MDX를 활용하여 현대적인 블로그를 구축하는 과정을 소개해드리려고 합니다. 이 글에서는 프로젝트 설정부터 완성까지의 전체 과정을 단계별로 설명하겠습니다.
Astro란 무엇인가?
Astro는 콘텐츠 중심의 웹사이트를 구축하기 위한 현대적인 웹 프레임워크입니다. 다음과 같은 특징을 가지고 있습니다:
- 아일랜드 아키텍처: 필요한 JavaScript만 전송하여 성능을 최적화합니다.
- 다양한 UI 프레임워크 지원: React, Vue, Svelte 등 원하는 프레임워크를 함께 사용할 수 있습니다.
- 서버 우선 렌더링: 빠른 페이지 로딩과 SEO 최적화를 제공합니다.
- 콘텐츠 중심: 마크다운, MDX 등의 콘텐츠 형식을 기본적으로 지원합니다.
MDX란 무엇인가?
MDX는 마크다운에 JSX를 추가한 형식으로, 마크다운의 간결함과 컴포넌트의 유연성을 결합한 포맷입니다. 블로그 글 작성 시 다음과 같은 이점이 있습니다:
- 마크다운의 간단한 문법으로 콘텐츠 작성
- 필요할 때 React 컴포넌트 삽입 가능
- 코드 블록에 구문 강조 지원
프로젝트 설정하기
1. Astro 프로젝트 생성
먼저 새로운 Astro 프로젝트를 생성합니다:
npm create astro@latest .
이 명령어를 실행하면 Astro 설치 마법사가 시작됩니다. 저는 기본 템플릿을 선택했습니다.
2. Tailwind CSS 설치
다음으로 Tailwind CSS를 설치합니다:
npx astro add tailwind
이 명령어는 Tailwind CSS를 설치하고 필요한 설정 파일을 자동으로 생성합니다.
3. MDX 지원 추가
MDX 지원을 추가하기 위해 다음 명령어를 실행합니다:
npx astro add mdx
4. 코드 구문 강조 설정
코드 블록의 구문 강조를 위해 Shiki를 설치하고 설정합니다:
npm install shiki
그리고 astro.config.mjs
파일을 다음과 같이 수정합니다:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import tailwindcss from '@tailwindcss/vite';
import mdx from '@astrojs/mdx';
export default defineConfig({
vite: {
plugins: [tailwindcss()]
},
integrations: [
mdx({
syntaxHighlight: 'shiki',
shikiConfig: {
theme: 'github-dark',
wrap: true
},
remarkPlugins: [],
rehypePlugins: []
})
]
});
블로그 구조 설계하기
블로그를 구축할 때 다음과 같은 구조로 설계했습니다:
콘텐츠 구성
src/content/blog/
: MDX 블로그 포스트 저장src/content/config.ts
: 콘텐츠 컬렉션 설정
페이지 구성
src/pages/index.astro
: 홈페이지src/pages/blog/index.astro
: 블로그 목록 페이지src/pages/blog/[page].astro
: 페이지네이션 페이지src/pages/blog/[...slug].astro
: 블로그 포스트 상세 페이지src/pages/about.astro
: 소개 페이지src/pages/portfolio.astro
: 포트폴리오 페이지
컴포넌트 구성
src/components/Header.astro
: 헤더 컴포넌트src/components/Footer.astro
: 푸터 컴포넌트src/components/BlogPostCard.astro
: 블로그 포스트 카드 컴포넌트src/components/Pagination.astro
: 페이지네이션 컴포넌트
레이아웃 구성
src/layouts/Layout.astro
: 기본 레이아웃src/layouts/BlogPostLayout.astro
: 블로그 포스트 레이아웃
주요 기능 구현하기
1. 콘텐츠 컬렉션 설정
Astro의 콘텐츠 컬렉션을 사용하여 블로그 포스트를 관리합니다:
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blogCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.date(),
updatedDate: z.date().optional(),
heroImage: z.string().optional(),
tags: z.array(z.string()).default([]),
}),
});
export const collections = {
'blog': blogCollection,
};
2. 블로그 목록 페이지 구현
블로그 목록 페이지는 다음과 같이 구현했습니다:
// src/pages/blog/index.astro
---
import Layout from '../../layouts/Layout.astro';
import { getCollection } from 'astro:content';
import BlogPostCard from '../../components/BlogPostCard.astro';
import Pagination from '../../components/Pagination.astro';
// 페이지네이션 설정
const POSTS_PER_PAGE = 6;
const currentPage = 1;
// 블로그 포스트 가져오기 및 정렬
const allPosts = await getCollection('blog');
const sortedPosts = allPosts.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
// 페이지네이션 계산
const totalPosts = sortedPosts.length;
const totalPages = Math.ceil(totalPosts / POSTS_PER_PAGE);
const paginatedPosts = sortedPosts.slice(0, POSTS_PER_PAGE);
---
<Layout title="블로그 포스트" description="모든 블로그 포스트 보기">
<div class="max-w-4xl mx-auto">
<header class="mb-8">
<h1 class="text-3xl font-bold text-gray-900 mb-2">블로그 포스트</h1>
<p class="text-gray-600">모든 글과 튜토리얼 보기</p>
</header>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12">
{paginatedPosts.map((post) => (
<BlogPostCard
title={post.data.title}
description={post.data.description}
pubDate={post.data.pubDate}
url={`/blog/${post.slug}`}
tags={post.data.tags}
/>
))}
</div>
{totalPages > 1 && (
<Pagination currentPage={currentPage} totalPages={totalPages} baseUrl="/blog" />
)}
</div>
</Layout>
3. 블로그 포스트 상세 페이지 구현
블로그 포스트 상세 페이지는 다음과 같이 구현했습니다:
// src/pages/blog/[...slug].astro
---
import { getCollection } from 'astro:content';
import BlogPostLayout from '../../layouts/BlogPostLayout.astro';
import type { GetStaticPathsResult } from 'astro';
export async function getStaticPaths(): Promise<GetStaticPathsResult> {
const blogEntries = await getCollection('blog');
return blogEntries.map(entry => ({
params: { slug: entry.slug },
props: { entry },
}));
}
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<BlogPostLayout post={entry}>
<Content />
</BlogPostLayout>
스타일링
Tailwind CSS v4를 사용하여 블로그의 스타일을 구현했습니다. 기본적인 설정은 다음과 같습니다:
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
theme: {
extend: {},
},
plugins: [],
}
어려웠던 점과 해결 방법
1. TypeScript 오류 처리
Astro와 MDX를 함께 사용할 때 TypeScript 오류가 발생했습니다. 특히 map
함수 내에서 매개변수 타입을 명시하지 않았을 때 오류가 발생했습니다. 이를 해결하기 위해 명시적으로 타입을 지정했습니다:
{posts.map((post: any) => (
<BlogPostCard
title={post.data.title}
description={post.data.description}
pubDate={post.data.pubDate}
url={`/blog/${post.slug}`}
tags={post.data.tags}
/>
))}
2. 페이지네이션 구현
페이지네이션을 구현할 때 현재 페이지와 총 페이지 수를 계산하는 로직이 복잡했습니다. 이를 해결하기 위해 별도의 컴포넌트로 분리하고, 페이지 번호에 따라 URL을 생성하는 로직을 구현했습니다.
결론
Astro와 MDX를 사용하여 블로그를 구축하는 과정은 매우 즐거웠습니다. Astro의 콘텐츠 중심 접근 방식과 MDX의 유연성 덕분에 빠르고 효율적으로 블로그를 구축할 수 있었습니다.
특히 다음과 같은 이점이 있었습니다:
- 빠른 개발 속도: Astro의 직관적인 API와 통합 기능 덕분에 빠르게 개발할 수 있었습니다.
- 성능 최적화: 아일랜드 아키텍처를 통해 필요한 JavaScript만 전송하여 성능이 뛰어납니다.
- 콘텐츠 관리 용이성: MDX를 통해 마크다운의 간결함과 컴포넌트의 유연성을 모두 활용할 수 있었습니다.
- 확장성: 필요에 따라 다양한 기능을 쉽게 추가할 수 있는 구조입니다.
앞으로 이 블로그에 더 많은 기능을 추가하고 개선해 나갈 계획입니다. 여러분도 Astro와 MDX를 활용하여 자신만의 블로그를 만들어보세요!