Astro 블로그 카테고리와 아일랜드 사이드바 구현하기
블로그를 구축하면서 포스트를 구조적으로 분류할 카테고리 시스템 의 필요성을 느꼈다. 단순히 태그를 나열하는 것을 넘어, 벨로그(Velog)나 네이버 블로그처럼 명확한 카테고리를 제공하고 싶었다.
이에 더해 데스크탑 환경에서는 화면 스크롤을 따라다니는 아일랜드 사이드바를 배치하고, 메인 포스트 목록은 화면 정중앙에 고정되도록 UI를 설계했다. 이 과정을 단계별로 정리해 본다.
1. Zod를 활용한 카테고리 스키마 정의
가장 먼저 할 일은 마크다운(.md) 파일의 프론트매터(Frontmatter)에 들어갈 카테고리 타입을 엄격하게 정의하는 것이다. Astro의 Content Collections 기능과 zod를 사용하면 오타로 인한 빌드 에러를 사전에 방지할 수 있다.
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
// z.enum을 사용하여 허용된 카테고리만 작성 가능하도록 강제한다.
category: z.enum(['Devlog', 'Blog-Build']).default('Devlog'),
tags: z.array(z.string()).optional(),
}),
});
export const collections = { blog };
2. 동적 라우팅과 알고리즘 최적화 (getStaticPaths)
카테고리별로 페이지를 생성하기 위해 src/pages/blog/category/[category].astro 파일을 생성했다.
여기서 한 가지 알고리즘적인 최적화를 진행했다. 전체 포스트를 무작정 최신순으로 정렬한 뒤 카테고리를 추출하는 대신, 각 카테고리별로 먼저 데이터를 필터링하여 크기를 줄인 후 정렬(Sort)을 수행 하는 방식이다. 데이터가 커질수록 연산 비용을 줄일 수 있는 효율적인 접근이다.
---
import { getCollection } from "astro:content";
import Layout from "../../../layouts/Layout.astro";
export async function getStaticPaths() {
const allPosts = await getCollection("blog");
// 1. 존재하는 모든 카테고리 중복 없이 추출
const uniqueCategories = [
...new Set(allPosts.map((post) => post.data.category).filter(Boolean)),
];
// 2. 각 카테고리별로 페이지 경로와 데이터 전달
return uniqueCategories.map((category) => {
// 필터링을 먼저 수행하여 정렬할 배열의 크기를 줄인다.
const filteredPosts = allPosts.filter(
(post) => post.data.category === category
);
// 날짜 기준 최신순 정렬 (날짜가 같으면 파일명 역순)
const sortedFilteredPosts = filteredPosts.sort((a, b) => {
const dateDiff = b.data.pubDate.valueOf() - a.data.pubDate.valueOf();
if (dateDiff !== 0) return dateDiff;
return b.slug.localeCompare(a.slug);
});
return {
params: { category: category.toString() },
props: { posts: sortedFilteredPosts },
};
});
}
const { category } = Astro.params;
const { posts } = Astro.props;
---
3. 중앙 정렬을 지키는 3단 Flex 레이아웃
데스크탑 UI의 핵심은 “메인 콘텐츠는 화면 정중앙에 고정하되, 사이드바는 왼쪽에 둥둥 떠 있게 만드는 것” 이었다. fixed를 사용하면 화면 폭이 좁아질 때 레이아웃이 깨지는 문제가 발생하여, sticky 속성과 3단 Flex 레이아웃(양팔 저울 기법) 을 도입했다.
메인 콘텐츠 양옆에 flex-1을 가진 공간을 배치하여 완벽한 중앙 정렬을 구현했다. 또한 메인 페이지(index.astro)에서는 사이드바를 숨길 수 있도록 hideSidebar Props를 추가했다.
---
// src/layouts/Layout.astro
import Header from "../components/Header.jsx";
import Sidebar from "../components/Sidebar.astro";
import Footer from "../components/Footer.astro";
interface Props {
title: string;
hideSidebar?: boolean; // 사이드바 숨김 여부 제어
}
const { title, hideSidebar = false } = Astro.props;
---
<html lang="ko">
<body class="bg-gray-50 dark:bg-zinc-900 min-h-screen flex flex-col font-sans">
<Header client:load />
<div class="flex justify-center w-full mt-12 px-4 sm:px-6">
{/* 1. 왼쪽 사이드바 영역 */}
{!hideSidebar && (
<div class="hidden lg:flex flex-1 justify-end pr-8 xl:pr-12">
<Sidebar />
</div>
)}
{/* 2. 메인 콘텐츠 (정중앙 고정) */}
<main class="w-full max-w-3xl shrink-0 pb-16">
<slot />
</main>
{/* 3. 오른쪽 투명 더미 영역 (왼쪽과 대칭을 맞추어 메인을 중앙으로 밀어줌) */}
{!hideSidebar && (
<div class="hidden lg:block flex-1 pl-8 xl:pl-12"></div>
)}
</div>
<Footer />
</body>
</html> 이 포스팅은 AI의 도움을 받아 초안을 작성하고, 직접 검수 및 편집한 글입니다.