[Astro] Pagefind를 이용한 검색 구현

2026-04-01 hit count image

Astro 블로그에 Pagefind를 이용한 정적 검색 기능을 구현하는 방법을 공유합니다. 빌드 통합, 다국어 검색, 카테고리 필터링 등을 다룹니다.

astro

개요

이전 글 광고 연동 (Google AdSense)에서 광고 연동을 다루었습니다. 이번 글에서는 정적 사이트에서 서버 없이 검색 기능을 구현하는 방법을 설명합니다.

Pagefind란

Pagefind는 정적 사이트를 위한 검색 라이브러리입니다. 빌드된 HTML 파일을 분석하여 검색 인덱스를 생성하고, 클라이언트 사이드에서 검색을 수행합니다.

주요 특징

다른 검색 솔루션(Algolia, Elasticsearch 등)과 달리 별도 서버 없이 정적 사이트만으로 동작하는 것이 Pagefind의 가장 큰 장점입니다.

  • 서버 불필요: 빌드 시 인덱스를 생성하여 정적 파일로 제공
  • 경량: 검색 인덱스가 작고 JavaScript 번들도 가벼움
  • 빠른 검색: WebAssembly 기반으로 브라우저에서 빠르게 동작
  • 다국어 지원: 언어별 인덱싱 가능

설치

Pagefind는 빌드 시에만 사용되므로 개발 의존성(-D)으로 설치합니다.

npm install -D pagefind

빌드 통합

package.jsonbuild 스크립트에서 Astro 빌드 후 Pagefind 인덱싱을 실행합니다.

{
  "scripts": {
    "build": "astro build && npx pagefind --site dist"
  }
}

Pagefinddist/ 디렉토리의 HTML 파일을 분석하여 dist/pagefind/ 디렉토리에 검색 인덱스를 생성합니다. 따라서 반드시 astro build 이후에 실행해야 합니다.

검색 대상 지정

Pagefind는 기본적으로 페이지의 모든 텍스트를 인덱싱합니다. 하지만 네비게이션, 푸터 등 본문이 아닌 영역까지 검색되면 불필요한 결과가 노출될 수 있습니다. data-pagefind-body 속성을 사용하면 검색 대상을 본문 영역으로 한정할 수 있습니다.

<!-- src/layouts/PostLayout.astro -->
<article data-pagefind-body>
  <slot />
  <!-- 마크다운 콘텐츠 -->
</article>

검색에서 제외할 요소에는 data-pagefind-ignore 속성을 사용합니다.

메타데이터 필터

검색 결과를 카테고리별로 필터링하려면 data-pagefind-filter 속성으로 메타데이터를 지정합니다. 이 속성이 포함된 요소의 텍스트가 필터 값으로 사용됩니다.

<!-- src/layouts/PostLayout.astro -->
<span data-pagefind-filter="category">{category}</span>

검색 페이지 구현

검색 기능은 SearchBar.astro(홈페이지 검색바)와 SearchPage.astro(전용 검색 페이지)로 구현됩니다.

SearchPage.astro 주요 구조

검색 페이지는 카테고리 필터 칩, 검색 상태 표시, 검색 결과 목록, 페이지네이션으로 구성됩니다. 서버 사이드에서 카테고리 목록과 번역 데이터를 준비하고, 실제 검색은 클라이언트 사이드 JavaScript로 처리합니다.

---
// src/components/SearchPage.astro
import { useTranslations, getLocalizedPath } from '../i18n/translations';
import { getMainCategories } from '../data/categories';

const { lang } = Astro.props;
const t = useTranslations(lang);
const mainCategories = getMainCategories();
---

<div class="search-page">
  <!-- 카테고리 필터 칩 -->
  <div class="search-page__filters" id="search-filters">
    <button class="search-page__chip active" data-category="">
      {t('search.allCategories')}
    </button>
    {categoryChips.map((chip) => (
      <button class="search-page__chip" data-category={chip.slug}>
        {chip.title}
      </button>
    ))}
  </div>

  <!-- 검색 상태 -->
  <div class="search-page__status" id="search-status"></div>

  <!-- 검색 결과 -->
  <div class="search-page__results" id="search-results"></div>

  <!-- 페이지네이션 -->
  <nav id="search-pagination" class="search-page__pagination"></nav>
</div>

클라이언트 사이드 검색 로직

실제 검색은 브라우저에서 실행됩니다. Pagefind의 JavaScript API를 사용하여 검색어를 입력하면 빌드 시 생성된 인덱스에서 결과를 찾아 반환합니다.

// src/components/SearchPage.astro 내 클라이언트 스크립트
const pagefind = await import('/pagefind/pagefind.js');
await pagefind.init();

// 검색 실행
const search = await pagefind.search(query);

// 결과 로드
const results = await Promise.all(search.results.map((r) => r.data()));

URL 쿼리 파라미터(?q=검색어)를 사용하여 검색어를 유지하므로, 검색 결과 페이지를 북마크하거나 공유할 수 있습니다.

다국어 검색

이 블로그는 일본어, 한국어, 영어를 지원하므로 각 언어별로 별도의 검색 페이지를 제공합니다.

언어검색 페이지 URL
일본어/search/
한국어/ko/search/
영어/en/search/

Pagefind의 인덱스에는 모든 언어의 콘텐츠가 포함되지만, 검색 시 해당 언어의 결과만 필터링하여 표시합니다.

카테고리 필터링

검색 결과가 많을 때 원하는 카테고리만 골라볼 수 있으면 편리합니다. categories.ts에 정의된 카테고리 목록을 칩 버튼으로 표시하고, 사용자가 선택한 카테고리로 결과를 필터링합니다.

// src/components/SearchPage.astro 내 클라이언트 스크립트
const search = await pagefind.search(query, {
  filters: {
    category: selectedCategory,
  },
});

Jekyll 검색과의 비교

현재 블로그는 Jekyll로 운영하다가 Astro로 마이그레이션 중입니다. Jekyll 검색 구현에 대해서는 검색창 만들기 포스트를 참고하세요.

Jekyll과 Astro(Pagefind) 검색 구현의 주요 차이점을 비교하면 다음과 같습니다.

항목JekyllAstro (Pagefind)
검색 방식JSON 파일 기반 클라이언트 검색WebAssembly 기반 인덱스 검색
인덱스 크기전체 콘텐츠를 JSON으로 생성 (크기 큼)최적화된 바이너리 인덱스 (작음)
검색 속도JavaScript 문자열 매칭WebAssembly로 빠른 검색
설정 복잡도커스텀 JSON 생성 + 검색 로직 필요npx pagefind 한 줄로 인덱스 생성
카테고리 필터직접 구현 필요data-pagefind-filter 속성으로 간편 구현

완료

이번 글에서는 Pagefind를 이용한 정적 검색 구현을 살펴보았습니다.

  • Pagefind로 서버 없이 클라이언트 사이드 검색 구현
  • astro build && npx pagefind --site dist로 빌드 파이프라인 통합
  • data-pagefind-body, data-pagefind-filter로 검색 대상과 필터 지정
  • 언어별 검색 페이지로 다국어 검색 지원
  • 카테고리 칩 버튼으로 결과 필터링

다음 글 레이아웃과 컴포넌트 아키텍처에서는 블로그의 레이아웃과 컴포넌트 구조를 다룹니다.

시리즈 안내

이 포스트는 Jekyll에서 Astro로 마이그레이션 시리즈의 일부입니다.

  1. Jekyll에서 Astro로 마이그레이션한 이유
  2. Astro 설치 및 프로젝트 구성
  3. Content Collections와 마크다운 마이그레이션
  4. 다국어(i18n) 구현
  5. SEO 구현
  6. 이미지 최적화 — 커스텀 rehype 플러그인
  7. 댓글 시스템 (Utterances)
  8. 광고 연동 (Google AdSense)
  9. Pagefind를 이용한 검색 구현
  10. 레이아웃과 컴포넌트 아키텍처
  11. GitHub Pages 배포
  12. 소셜 공유 자동화 스크립트
  13. 트러블슈팅과 팁

제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!

앱 홍보

책 홍보

블로그를 운영하면서 좋은 기회가 생겨 책을 출판하게 되었습니다.

아래 링크를 통해 제가 쓴 책을 구매하실 수 있습니다.
많은 분들에게 도움이 되면 좋겠네요.



SHARE
Twitter Facebook RSS