How to Use Pagefind with Astro: Complete Guide
Step-by-step guide to integrating Pagefind with your Astro website. Installation, configuration, and best practices.
Pagefind is a static search library that indexes your site at build time and runs entirely in the browser. No server, no API key, no monthly bill. It is the perfect search solution for Astro because both tools embrace the static-first approach. Your search index is just a set of files shipped alongside your site. Astro's own documentation framework, Starlight, ships Pagefind as its built-in search, so this pairing is battle-tested in production.
Versions referenced in this guide (verified on the npm registry on 2026-05-29): Pagefind 1.5.2, Astro 6.4.2, and the community integration astro-pagefind 2.0.0.
Prerequisites
- Node.js
22.12.0or newer. Astro 6 raised its minimum supported Node version, so the old "Node 18 is fine" advice no longer holds. - An Astro project (
npm create astro@latest) - A site with some content (blog posts, docs, or pages to search through)
A note on rendering mode. Pagefind indexes the static HTML Astro produces, so it works best with prerendered pages. Astro 6 prerenders every page by default (there is no separate output: 'static' flag to set, and output: 'hybrid' was removed back in Astro 5, merged into the default static behavior). If a given route opts into on-demand rendering with export const prerender = false, that route's HTML does not exist at build time and Pagefind cannot index it. Keep the pages you want searchable prerendered.
Installation
There are two supported paths. Pick one.
Option A: The official integration (recommended)
The astro-pagefind integration runs Pagefind automatically on every astro build, so you never wire up a postbuild script by hand.
npm i astro-pagefind
Add it to your Astro config:
// astro.config.mjs
import { defineConfig } from "astro/config";
import pagefind from "astro-pagefind";
export default defineConfig({
integrations: [pagefind()],
});
After this, Pagefind indexes your built site every time you run npm run build. No extra scripts needed.
Option B: Pagefind CLI directly
If you prefer no integration, install Pagefind as a dev dependency and run it as a postbuild step:
npm install -D pagefind
Then add a postbuild script to your package.json:
{
"scripts": {
"build": "astro build",
"postbuild": "pagefind --site dist"
}
}
This runs Pagefind after every Astro build, indexing the HTML files in your dist directory. The Pagefind docs also show invoking it with npx -y pagefind --site dist directly on your build host, which is handy when your CI cannot persist a dev dependency.
Configuration
Pagefind works with zero configuration out of the box. It crawls all your HTML pages and indexes the text content. If you want to customize its behavior, create a pagefind.yml in your project root:
# pagefind.yml
site: dist
glob: "**/*.html"
exclude_selectors:
- "nav"
- "footer"
- ".sidebar"
The exclude_selectors option tells Pagefind to skip navigation, footer, and sidebar content so search results only contain your main content.
Basic Usage
Pagefind ships two prebuilt UI bundles. The classic Default UI (pagefind-ui.js / PagefindUI) is shown below and still works. Pagefind 1.5.0 also introduced a newer Component UI (pagefind-component-ui.js, with <pagefind-modal-trigger> and <pagefind-modal> custom elements) that the docs now point new users toward. Both bundles are generated into dist/pagefind/ at build time. The Default UI is the simplest drop-in, so we use it here.
Add the Pagefind UI to your site. Create a search page or component:
---
// src/pages/search.astro
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout title="Search">
<main class="max-w-3xl mx-auto px-4 py-12">
<h1 class="text-3xl font-bold mb-8">Search</h1>
<div id="search"></div>
</main>
</BaseLayout>
<link href="/pagefind/pagefind-ui.css" rel="stylesheet" />
<script is:inline>
import("/pagefind/pagefind-ui.js").then((module) => {
new module.PagefindUI({
element: "#search",
showSubResults: true,
showImages: false,
});
});
</script>
After running npm run build, Pagefind generates its index and UI assets in the dist/pagefind/ directory. The search widget loads lazily and only fetches index chunks as the user types.
Controlling What Gets Indexed
Use data-pagefind-body to tell Pagefind which part of your pages to index:
---
// src/layouts/BlogPost.astro
const { title, description } = Astro.props;
---
<html>
<body>
<nav><!-- not indexed --></nav>
<article data-pagefind-body>
<h1>{title}</h1>
<slot />
</article>
<footer><!-- not indexed --></footer>
</body>
</html>
When you add data-pagefind-body to any element, Pagefind switches from indexing the entire page to only indexing elements with that attribute.
You can also add metadata for filtering:
<article
data-pagefind-body
data-pagefind-meta="category:tutorials"
data-pagefind-filter="tag:astro"
>
<h1>{title}</h1>
<slot />
</article>
Using the JavaScript API
For a custom search UI (not the default widget), use the Pagefind JavaScript API directly:
<input type="text" id="search-input" placeholder="Search..." />
<div id="results"></div>
<script is:inline>
let pagefind;
async function initSearch() {
pagefind = await import("/pagefind/pagefind.js");
await pagefind.init();
}
initSearch();
document.getElementById("search-input").addEventListener("input", async (e) => {
const query = e.target.value;
const container = document.getElementById("results");
if (!query) {
container.textContent = "";
return;
}
// pagefind.debouncedSearch(query, options, timeout) waits a short window
// (default 300ms) and returns null if a newer keystroke supersedes this
// one, which avoids firing a request on every character. Use it instead
// of pagefind.search for type-as-you-go inputs.
const search = await pagefind.debouncedSearch(query);
if (!search) return; // debouncedSearch returns null if superseded
const results = await Promise.all(
search.results.slice(0, 10).map((r) => r.data())
);
container.textContent = "";
results.forEach((r) => {
const link = document.createElement("a");
link.href = r.url;
const heading = document.createElement("h3");
heading.textContent = r.meta.title;
const excerpt = document.createElement("p");
excerpt.textContent = r.excerpt;
link.appendChild(heading);
link.appendChild(excerpt);
container.appendChild(link);
});
});
</script>
Production Tips
Use
data-pagefind-bodyon your content wrapper. Without it, Pagefind indexes everything including headers, footers, and navigation. Narrowing the scope gives much better search results.
Add data-pagefind-ignore to boilerplate. For elements inside your indexed body that should be skipped (like table of contents or share buttons), add data-pagefind-ignore.
The index is lazy-loaded. Pagefind only loads a few KB initially and fetches index chunks as needed. Your page load performance is not affected by having thousands of pages indexed.
Test search during development. Run npm run build first, then npm run preview to test search locally. The search index only exists after a build.
Use multilingual support. Pagefind automatically detects the lang attribute on your HTML element and creates separate indexes per language. No extra configuration needed for multilingual Astro sites.
Alternatives to Consider
- Algolia if you need hosted search with advanced features like typo tolerance, faceting, and analytics. More powerful but costs money at scale.
- Meilisearch if you want a self-hosted search engine with a great developer experience. Requires a server to run.
- Orama if you want a full-text search engine that runs in the browser with vector search capabilities.
Common Errors and Fixes
"Failed to fetch dynamically imported module: /pagefind/pagefind.js" in development. The /pagefind/ directory only exists after a build. Pagefind runs against the built HTML in dist, so npm run dev has no index to load. Run npm run build then npm run preview to test search locally. If you use the astro-pagefind integration, it wires a dev-server mock so the import resolves, but real results still require a completed build.
Empty or partial search index. If you added data-pagefind-body to one template, Pagefind switches from whole-page indexing to indexing only elements that carry that attribute, across the whole site. Pages without it then index nothing. Either add data-pagefind-body to every content template or remove it everywhere and let Pagefind index full pages.
TypeScript complains it cannot find the module /pagefind/pagefind.js. That path is generated at build time and is not a real source module, so the type checker has no declarations for it. The Pagefind docs suggest suppressing the import with a directive such as // @ts-expect-error - pagefind generated at build time or declaring the module yourself. The runtime import still works.
Search returns nothing after a route was converted to on-demand rendering. A route with export const prerender = false produces no static HTML at build time, so Pagefind cannot crawl it. Keep searchable routes prerendered (the Astro 6 default).
Pagefind binary fails to download or run in CI. Pagefind ships as a platform-specific binary fetched on install. On locked-down or unusual CI images the postinstall can fail. Invoking npx -y pagefind --site dist at build time, as the official docs show, fetches the correct binary on demand and sidesteps a stale install cache.
pagefind.yml ignored. The config file must sit at the path Pagefind is invoked from and uses snake_case keys (for example exclude_selectors, not excludeSelectors). A typo'd key is silently skipped rather than erroring.
Official Docs and Examples
- Pagefind documentation home: https://pagefind.app/docs/
- Pagefind JavaScript API reference: https://pagefind.app/docs/api/
- Pagefind UI reference: https://pagefind.app/docs/ui/
- Pagefind source and example fixtures: https://github.com/Pagefind/pagefind
- The
astro-pagefindcommunity integration (install + config): https://github.com/shishkin/astro-pagefind - Astro Starlight site-search guide (Pagefind is Starlight's default, a real production example): https://starlight.astro.build/guides/site-search/
- Starlight source repository: https://github.com/withastro/starlight
- Astro on-demand rendering and default static behavior: https://docs.astro.build/en/guides/on-demand-rendering/
Wrapping Up
Pagefind is the best search solution for most Astro sites. It is free, requires no server, builds in seconds, and delivers fast, relevant results. Add it to your build pipeline once and forget about it.
Sources
- Pagefind documentation (checked-on 2026-05-29)
- Pagefind JavaScript API reference (checked-on 2026-05-29)
- Pagefind UI reference (checked-on 2026-05-29)
- Pagefind on GitHub (checked-on 2026-05-29)
- pagefind on the npm registry, version 1.5.2 (checked-on 2026-05-29)
- astro-pagefind integration on GitHub (checked-on 2026-05-29)
- astro-pagefind on the npm registry, version 2.0.0 (checked-on 2026-05-29)
- astro on the npm registry, version 6.4.2 and Node engine requirement (checked-on 2026-05-29)
- Astro on-demand rendering guide, static is the default (checked-on 2026-05-29)
- Astro v5 upgrade guide, output hybrid removed and merged into static (checked-on 2026-05-29)
- Astro Starlight site-search guide, Pagefind as built-in search (checked-on 2026-05-29)
Related Articles
How to Use Algolia with Astro: Complete Guide
Step-by-step guide to integrating Algolia with your Astro website.
How to Integrate Auth0 with Astro: Complete Guide
Step-by-step guide to integrating Auth0 with your Astro website. Setup, configuration, and best practices.
How to Use AWS Amplify with Astro: Complete Guide
Step-by-step guide to integrating AWS Amplify with your Astro website.