개요
이전 글 광고 연동 (Google AdSense)에서 광고 연동을 다루었습니다. 이번 글에서는 정적 사이트에서 서버 없이 검색 기능을 구현하는 방법을 설명합니다.
Pagefind란
Pagefind는 정적 사이트를 위한 검색 라이브러리입니다. 빌드된 HTML 파일을 분석하여 검색 인덱스를 생성하고, 클라이언트 사이드에서 검색을 수행합니다.
주요 특징
다른 검색 솔루션(Algolia, Elasticsearch 등)과 달리 별도 서버 없이 정적 사이트만으로 동작하는 것이 Pagefind의 가장 큰 장점입니다.
- 서버 불필요: 빌드 시 인덱스를 생성하여 정적 파일로 제공
- 경량: 검색 인덱스가 작고 JavaScript 번들도 가벼움
- 빠른 검색: WebAssembly 기반으로 브라우저에서 빠르게 동작
- 다국어 지원: 언어별 인덱싱 가능
설치
Pagefind는 빌드 시에만 사용되므로 개발 의존성(-D)으로 설치합니다.
npm install -D pagefind
빌드 통합
package.json의 build 스크립트에서 Astro 빌드 후 Pagefind 인덱싱을 실행합니다.
{
"scripts": {
"build": "astro build && npx pagefind --site dist"
}
}
Pagefind는 dist/ 디렉토리의 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) 검색 구현의 주요 차이점을 비교하면 다음과 같습니다.
| 항목 | Jekyll | Astro (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로 마이그레이션 시리즈의 일부입니다.
- Jekyll에서 Astro로 마이그레이션한 이유
- Astro 설치 및 프로젝트 구성
- Content Collections와 마크다운 마이그레이션
- 다국어(i18n) 구현
- SEO 구현
- 이미지 최적화 — 커스텀 rehype 플러그인
- 댓글 시스템 (Utterances)
- 광고 연동 (Google AdSense)
- Pagefind를 이용한 검색 구현
- 레이아웃과 컴포넌트 아키텍처
- GitHub Pages 배포
- 소셜 공유 자동화 스크립트
- 트러블슈팅과 팁
제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!
앱 홍보
Deku가 개발한 앱을 한번 사용해보세요.Deku가 개발한 앱은 Flutter로 개발되었습니다.관심있으신 분들은 앱을 다운로드하여 사용해 주시면 정말 감사하겠습니다.