[Astro] Search Implementation with Pagefind

2026-04-01 hit count image

Sharing how to implement static search functionality using Pagefind in an Astro blog. Covers build integration, multilingual search, category filtering, and more.

astro

Overview

In the previous post Ad Integration (Google AdSense), we covered ad integration. In this post, I’ll explain how to implement search functionality on a static site without a server.

What is Pagefind

Pagefind is a search library for static sites. It analyzes built HTML files to generate a search index and performs searches on the client side.

Key Features

Unlike other search solutions (Algolia, Elasticsearch, etc.), the biggest advantage of Pagefind is that it works with just a static site — no separate server required.

  • No Server Required: Generates indexes at build time and serves them as static files
  • Lightweight: Small search index and light JavaScript bundle
  • Fast Search: Runs quickly in the browser using WebAssembly
  • Multilingual Support: Language-specific indexing available

Installation

Since Pagefind is only used during build, install it as a dev dependency (-D).

npm install -D pagefind

Build Integration

The Pagefind indexing runs after the Astro build in the build script of package.json.

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

Pagefind analyzes HTML files in the dist/ directory and generates search indexes in the dist/pagefind/ directory. Therefore, it must be run after astro build.

Specifying Search Targets

By default, Pagefind indexes all text on a page. However, if non-body areas like navigation and footer are also searched, unnecessary results may be exposed. Using the data-pagefind-body attribute, you can limit the search target to the body area only.

<!-- src/layouts/PostLayout.astro -->
<article data-pagefind-body>
  <slot />
  <!-- Markdown content -->
</article>

Use the data-pagefind-ignore attribute on elements to exclude from search.

Metadata Filters

To filter search results by category, specify metadata with the data-pagefind-filter attribute. The text of the element containing this attribute is used as the filter value.

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

Search Page Implementation

Search functionality is implemented with SearchBar.astro (homepage search bar) and SearchPage.astro (dedicated search page).

SearchPage.astro Main Structure

The search page consists of category filter chips, search status display, search results list, and pagination. Category lists and translation data are prepared on the server side, while actual searching is handled by client-side 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">
  <!-- Category filter chips -->
  <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>

  <!-- Search status -->
  <div class="search-page__status" id="search-status"></div>

  <!-- Search results -->
  <div class="search-page__results" id="search-results"></div>

  <!-- Pagination -->
  <nav id="search-pagination" class="search-page__pagination"></nav>
</div>

Client-Side Search Logic

The actual search runs in the browser. Using Pagefind’s JavaScript API, when a search query is entered, it finds and returns results from the index generated at build time.

// Client script in src/components/SearchPage.astro
const pagefind = await import('/pagefind/pagefind.js');
await pagefind.init();

// Execute search
const search = await pagefind.search(query);

// Load results
const results = await Promise.all(search.results.map((r) => r.data()));

Since URL query parameters (?q=search term) are used to persist the search query, the search results page can be bookmarked or shared.

This blog supports Japanese, Korean, and English, so separate search pages are provided for each language.

LanguageSearch Page URL
Japanese/search/
Korean/ko/search/
English/en/search/

Pagefind’s index includes content from all languages, but only results for the current language are filtered and displayed during search.

Category Filtering

When there are many search results, it’s convenient to be able to pick only the categories you want. Category lists defined in categories.ts are displayed as chip buttons, and results are filtered by the category the user selects.

// Client script in src/components/SearchPage.astro
const search = await pagefind.search(query, {
  filters: {
    category: selectedCategory,
  },
});

This blog was originally running on Jekyll and is being migrated to Astro. For the Jekyll search implementation, refer to the Creating a Search Bar post.

Here are the key differences between Jekyll and Astro (Pagefind) search implementations:

ItemJekyllAstro (Pagefind)
Search methodJSON file-based client searchWebAssembly-based index search
Index sizeFull content generated as JSON (large)Optimized binary index (small)
Search speedJavaScript string matchingFast search with WebAssembly
Setup complexityCustom JSON generation + search logic neededIndex generation with one npx pagefind command
Category filterManual implementation neededSimple implementation with data-pagefind-filter

Conclusion

In this post, we looked at implementing static search with Pagefind.

  • Implementing client-side search without a server using Pagefind
  • Integrating into the build pipeline with astro build && npx pagefind --site dist
  • Specifying search targets and filters with data-pagefind-body and data-pagefind-filter
  • Supporting multilingual search with language-specific search pages
  • Filtering results with category chip buttons

In the next post Layout and Component Architecture, we’ll cover the blog’s layout and component structure.

Series Guide

This post is part of the Jekyll to Astro migration series.

  1. Why I Migrated from Jekyll to Astro
  2. Astro Installation and Project Setup
  3. Content Collections and Markdown Migration
  4. Multilingual (i18n) Implementation
  5. SEO Implementation
  6. Image Optimization — Custom rehype Plugin
  7. Comment System (Utterances)
  8. Ad Integration (Google AdSense)
  9. Search Implementation with Pagefind
  10. Layout and Component Architecture
  11. GitHub Pages Deployment
  12. Social Share Automation Script
  13. Troubleshooting and Tips

Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!

App promotion

You can use the applications that are created by this blog writer Deku.
Deku created the applications with Flutter.

If you have interested, please try to download them for free.



SHARE
Twitter Facebook RSS