Pagefind is a client-side search library designed for static websites. it allows you to add powerful search functionality to websites created with Static Site Generator (SSG) or sites built with frameworks like Next.js, Gatsby, Hugo, Jekyll, and more. it can be installed and configured locally without any external APIs or service keys, making it very easy and quick to get up and running.
how it works
Pagefind works in the following process
- indexing phase: After your site is built, it analyzes your HTML files to create a search index. during this phase, text content, titles, metadata, etc. are extracted and processed, and a JS file is generated that makes the indexed data available. this means that you can only use it if you generate HTML at build time.
- search API: After the index is created, you can use the provided JavaScript API to implement a search interface on your site.
- Creating the UI: When a user enters a search query, Pagefind uses the pre-created index to quickly find relevant pages and sections to provide results, and we only need to implement the UI using these results.
you can learn more about the features and how to use them in the Docs.
Getting Started with Pagefind | Pagefind - Static low-bandwidth search at scale
quick start!
write a script
let's cut to the chase and get started. You don't need to install any packages, just follow the steps below.The development environment is using 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 in order to be indexed, HTML needs to be generated, and in order for HTML to be generated, next needs to be built. after adding this script and running the build, you should see the pagefind-related files under the public folder.
now we just need to import pagefind.js
and use it.
API Call
first, here's the full 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 {
// Try to load dynamically at runtime
setPagefind(
await import(
// @ts-expect-error
"./pagefind/pagefind.js"
),
);
} catch (error) {
console.error("Pagefind initialization failed:", error);
}
};
// Run only on the 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
<input
type="text"
value={search}
onChange={handleSearch}
placeholder="Please enter a search term..."
/>
<div>
{results.map((result, i) => (
<div key={result.url}>
<a href={result.url}>{result.meta.title}</a>
</div>
))}
</div>
</div>
);
}
Create the UI
now that we've made the call, let's see the result.
the main things you'll want to look for are
- excerpt: This is a partial parse of the keyword within the content.
- meta: Gets the meta information within the content: for title, the first
h1
tag encountered, for image, the firstimage
tag encountered after theh1
tag.
all of these can be customized with additional options, so take a look at the docs!
problems
my blog is available in four languages (Korean, English, Chinese, and Japanese), and the search was showing posts in all languages.
this is not uncommon for multilingual pages, so of course pagefind provides a multilingual search feature. however, it was based on the value of the lang
attribute of the html
tag.
but in Next.js, you have to set it in Layout, and as far as I can find, there is no way to get the lang value when building a static page... (If there is, please let me know)
workaround
so, I took a workaround and used the filter function in pagefind. i added the following additional attributes to the h1
tag of each page
<h1
data-pagefind-filter="lang[data-lang]"
data-lang={params.lang}
className="text-3xl md:text-5xl font-bold"
>
{title}
</h1>
and when we make the API request, we can add filters
const search = await pagefind.search(e.target.value, {
filters: {
lang: "en",
},
});
conclusion
we've seen how to quickly implement a search function using the small and lightweight library pagefind. creating your own search index without relying on external services provides privacy and control, and it also provides a fast and responsive user experience, which is very useful if you're running a blog or documentation site.