Add search to your Next.js static site with Pagefind
Pagefind is a neat OSS client-side search library that just recently reached
v1.0.0 in September ’23. From the docs:
Pagefind is a fully static search library that aims to perform well on large sites, while using as little of your users’ bandwidth as possible, and without hosting any infrastructure.
The docs do a terrific job of explaining things, and the search experience feels really snappy (hit
⌘+K to give it a try). To get Pagefind working with Next.js, it takes a few extra steps.
Run pagefind after build
Start by installing Pagefind as a dev dependency:
npm i -D pagefind
The Pagefind CLI generates a static search index from your site’s static files after building. The
--site flag indicates the output dir of your built static files, and the
--output-path option is where the search bundle will be written—more on that below. I’m on Next
13.4.9 and the
/pages router, so adjust as needed.
Depending on how you build and deploy, you might be able to avoid adding the dependency and postbuild script altogether by running
npx pagefind instead.
Dynamically import search bundle
The tricky bits that tripped me up were (1) handling errors in development, and (2) configuring Webpack to allow the import to resolve correctly.
The first one is an issue because the Pagefind search index is generated after build, so it doesn’t exist when running
next dev. One way to solve this is creating a
/pagefind/pagefind.js file that returns some mock search results in development, or you can add a try-catch and
@ts-expect-error comment to avoid creating the extra file (thanks to noxify for the tip!).
The second problem is that Webpack will bundle
/pagefind/pagefind.js and output the contents into a new file, so the import path will hit a dead end. That’s where the
webpackIgnore comment comes in handy, preserving the initial path. Files in the
/pages directory (in
v13.4.9, at least) will end up in the
.next/static/chunks/pages folder upon build, hence the custom
--output-path discussed above.
The search implementation on this website is public, so feel free to poke around (and suggest anything I could improve upon!).
Bells ’n whistles
Pagefind exposes some handy options for refining your index and polishing search UI. I am using both the
data-pagefind-ignore attributes to exclude certain content from my search index (e.g. header, 404 page, homepage, etc.). I also swapped
pagefind.debouncedSearch() in place of the standard
pagefind.search() to cut down on resource usage and prevent jarring flashes of results before you finish typing a search query.
Thanks to Otterlord’s blog post on using Pagefind with Astro for the nudge to share something similar for Next. If you’re keen to use Pagefind with another SSG, the docs have a list of community resources. There are also some nice demos to get a feel for the search experience before jumping into your own implementation.
And big, big thanks to bglw for helping me debug this and creating an amazing library!