Astro 블로그 카테고리 생성 최적화, reduce를 활용한 빌드 성능 향상

#Astro #TypeScript #JavaScript #Optimization #reduce

Astro 프레임워크를 사용하여 정적 블로그를 구축할 때, 카테고리별 포스트 목록 페이지를 생성하려면 getStaticPaths 를 사용해야 한다. 초기에는 단순하고 직관적인 코드로 기능 구현에 초점을 맞추었지만, 포스트 개수가 늘어남에 따라 빌드 성능이 저하될 수 있는 문제점을 발견했다.

이번 글에서는 자바스크립트의 reduce 를 활용하여 어떻게 이 과정을 효율적으로 개선했는지 정리해 본다.

1. 기존 코드의 문제점: 반복되는 필터링과 정렬

처음 작성했던 코드는 다음과 같은 흐름을 가지고 있었다.

  1. 모든 포스트에서 카테고리 이름만 추출하여 중복을 제거한 배열을 만든다.
  2. 이 배열을 순회하면서, 매번 전체 포스트를 대상으로 해당 카테고리와 일치하는지 확인(filter)한다.
  3. 필터링된 결과물을 다시 최신순으로 정렬(sort)한다.

이 방식은 카테고리가 M 개이고 전체 포스트가 N 개일 때, 대략 O(N * M) 의 비교 연산이 발생한다. 데이터가 적을 때는 문제가 없지만, 블로그 글이 수백 개로 쌓인다면 빌드 속도에 악영향을 미칠 수 있다.

2. 해결 방법: 단 한 번의 순회로 그룹화하기

이 문제를 해결하기 위해 reduce 를 활용하여 배열을 단 한 번만 훑는 방식으로 변경했다.

먼저 포스트 전체를 한 번 순회하면서, 각 카테고리 이름을 키(Key)로 하고 해당 카테고리에 속한 포스트 배열을 값(Value)으로 가지는 객체를 만든다. 이렇게 하면 굳이 filter 를 여러 번 호출할 필요가 없어진다.

또한, 타입스크립트 환경에서는 빈 객체({})를 누적기(Accumulator)로 사용할 때 인덱스 시그니처 에러가 발생할 수 있으므로, Record 를 사용하여 객체의 구조를 명확히 정의해 주어야 한다.

3. 정렬(Sorting) 최적화: 미리 한 번만 정렬하기

그룹화를 개선한 뒤, 정렬 로직도 최적화할 수 있었다. 기존에는 각 카테고리별로 나누어진 배열을 매번 정렬했다. 하지만 전체 포스트 배열을 맨 처음에 단 한 번 만 정렬해 두면, 이후에 reduce 로 나누어 담은 포스트들도 이미 정렬된 상태를 유지하게 된다.

날짜를 기준으로 최신순 정렬을 하고, 만약 날짜가 같다면 localeCompare 를 통해 슬러그(Slug) 기준 글자순 정렬이 되도록 2차 정렬 조건까지 추가했다.

4. 최종 최적화 코드

위의 고민들을 모두 반영하여 완성한 최종 코드는 다음과 같다.

---
import { getCollection } from "astro:content";
import Layout from "../../../layouts/Layout.astro";
import PostList from "../../../components/PostList.astro";

export async function getStaticPaths() {
  const allPosts = await getCollection("blog");

  // 1. 전체 포스트를 단 한 번만 최신순으로 정렬
  const sortedAllPosts = allPosts.sort((a, b) => {
    const dateDiff = b.data.pubDate.valueOf() - a.data.pubDate.valueOf();
    return dateDiff !== 0 ? dateDiff : b.slug.localeCompare(a.slug);
  });

  // 2. reduce를 활용하여 카테고리별로 포스트 그룹화 (O(N) 방식)
  const postsByCategory = sortedAllPosts.reduce(
    (acc, post) => {
      const category = post.data.category;
      if (category) {
        if (!acc[category]) acc[category] = [];
        acc[category].push(post);
      }
      return acc;
    },
    {} as Record<string, typeof sortedAllPosts>, // 타입 안전성 확보
  );

  // 3. 그룹화된 데이터를 바탕으로 경로와 props 반환
  return Object.entries(postsByCategory).map(([category, posts]) => {
    return {
      params: { category },
      props: { posts: posts },
    };
  });
}

const { category } = Astro.params;
const { posts } = Astro.props;
---

<Layout title=`${category} | Blog | 엄노니의 블로그`>
  <PostList posts={posts} category={category} />
</Layout>

자바스크립트의 reduceObject.entries 를 적절히 결합하면, 복잡하게 나뉘어 있던 데이터 처리 과정을 훨씬 간결하고 성능 좋게 최적화할 수 있다.

이 포스팅은 AI의 도움을 받아 초안을 작성하고, 직접 검수 및 편집한 글입니다.