Pagefind is a client-side search library designed for static websites. It allows you to add powerful search functionality to websites built with static site generators (SSG) or frameworks like Next.js, Gatsby, Hugo, Jekyll, etc. Since it can be installed and configured locally without external APIs or service keys, it can be applied very quickly and easily.
How It Works
Pagefind operates through the following process:
- Indexing Phase: After building the site, it analyzes HTML files to generate a search index. During this phase, text content, titles, metadata, etc. are extracted and processed, and a js file is created that allows the indexed data to be used. This means it can only be used when HTML is generated during the build process.
- Search API: After index generation, you can implement a search interface on your site using the provided JavaScript API.
- UI Generation: When a user enters a search term, Pagefind quickly finds relevant pages and sections using the pre-generated index and provides results, and we only need to implement the UI using these results.
Various features and usage instructions are available in the Docs.
Getting Started with Pagefind | Pagefind — Static low-bandwidth search at scale
Quick Start!
Writing the Script
Let's apply it right away without further ado. You don't need to install any packages, just follow the instructions below. My development environment uses Next.js 15
and pnpm
.
First, add postbuild
to your package.json
.
"scripts": {
// ...
"postbuild": "npx pagefind --site .next --output-path public/pagefind",
// ...
},
Keep in mind that for indexing to work, HTML must be generated, and for HTML to be generated, Next must be built. After adding this script and building, you can see pagefind-related files appear under the public folder.
Now you just need to import and use the pagefind.js
file that was generated here.
API Call
Here's the complete code:
export default function Search() {
const [search, setSearch] = useState("");
const [results, setResults] = useState<PagefindResult[]>([]);
const [pagefind, setPagefind] = useState<any>(null);
useEffect(() => {
const initPagefind = async () => {
try {
// Attempt to load dynamically at runtime
setPagefind(
await import(
// @ts-expect-error
"./pagefind/pagefind.js"
),
);
} catch (error) {
console.error("Pagefind initialization failed:", error);
}
};
// Execute only on client side
if (typeof window !== "undefined") {
initPagefind();
}
}, []);
const handleSearch = async (e: any) => {
setSearch(e.target.value);
if (!pagefind || e.target.value === "") {
setResults([]);
return;
}
const search = await pagefind.search(e.target.value);
const results = await Promise.all(search.results.map((r) => r.data()));
console.log(results);
setResults(results);
};
return (
<div>
<input
type="text"
value={search}
onChange={handleSearch}
placeholder="Enter search term..."
/>
<div>
{results.map((result, i) => (
<div key={result.url}>
<a href={result.url}>{result.meta.title}</a>
</div>
))}
</div>
</div>
);
}
UI Generation
Now that we've made the call, let's look at the results.
The main items you can examine are as follows:
- excerpt: This is a partial parsing of the content that contains the keyword.
- meta: This retrieves meta information from the content. For title, it's the first
h1
tag encountered, and for image, it's the firstimage
tag encountered after theh1
tag.
All of these can be customized through additional options, so check the Docs carefully!
Problem Encountered
My blog provides content in four languages: Korean, English, Chinese, and Japanese, but when searching, posts in all languages were appearing.
Since multilingual pages are a common case, Pagefind naturally provides a Multilingual search feature. However, it determines this based on what value is in the lang
attribute of the html
tag.
But in Next.js, this setting needs to be done in the Layout, and as far as I could find, there was no way to get the lang value during static page building... (Please let me know if there is a way)
Solution
So I used Pagefind's filter feature as a workaround. I added the following additional attributes to the h1
tag on each page:
<h1
data-pagefind-filter="lang[data-lang]"
data-lang={params.lang}
className="text-3xl md:text-5xl font-bold"
>
{title}
</h1>
And then added filters
when making API requests:
const search = await pagefind.search(e.target.value, {
filters: {
lang: "ko",
},
});
Conclusion
I quickly implemented search functionality using the small and lightweight library Pagefind. Since it generates search indexes independently without relying on external services, it has advantages in terms of privacy and control, and also provides fast response times from a user experience perspective, making it very useful for blogs or documentation sites.