Finally Making 14 Years of Posts Searchable

Finally Making 14 Years of Posts Searchable

Tutorials blogging pagefind search self-hosting static-sites

Happy new year, I guess, ten days late. I always mean to write a January 1st post and I never do, because January 1st is usually spent on the couch regretting whatever I ate on the 31st. This year the thing I actually got done over the break wasn't a resolution, it was fixing something on techpad that's bugged me since roughly 2015: this blog has never had real search.

For a long time that was fine. When you've got sixty posts, scrolling the archive page is search. But this is post... honestly I stopped counting a while back, somewhere north of 1,400 entries across fourteen-plus years, and the built-in "search" was a Google site: query I'd tell people to use in the comments. Embarrassing. So over a genuinely boring stretch between Christmas and New Year's I sat down and actually fixed it.

The tool: Pagefind

If you're running any kind of static site (mine's been on a homegrown static generator since I ditched Wordpress in 2018, but this works the same for Hugo, Eleventy, Jekyll, whatever), Pagefind is the thing. It's a static search library: no server, no API key, no monthly bill to some search-as-a-service company that will inevitably raise prices or get acquired. It crawls your already-built HTML output, builds a compact search index as static files, and ships a tiny bit of JS that queries that index client-side.

The setup is almost insultingly simple:

npx pagefind --site "public"

Point it at your build output directory, it chews through the HTML for a minute or two, and drops a pagefind folder full of index chunks into that same directory. Then you drop their search UI (or your own, if you want to style it) onto a page:

<link href="/pagefind/pagefind-ui.css" rel="stylesheet">
<script src="/pagefind/pagefind-ui.js"></script>
<div id="search"></div>
<script>
  new PagefindUI({ element: "#search" });
</script>

That's genuinely most of it. I ran it against my full build (1,400-ish posts, going back to November 2011) and the whole index came out under 4MB, which for full-text search across fourteen years of my rambling is kind of wild. My first Wordpress-era search plugin, for comparison, needed a MySQL query on every keystroke and would occasionally just fall over if two people searched at once. Two people. This blog gets maybe forty visitors a day.

The part that actually took time

The indexing itself was the easy hour. What took the rest of the afternoon was going through old posts and realizing how much cruft was going to show up in search results: old "linkdump" posts that were just five bullet points and no context, a handful of drafts I'd accidentally published and never un-published, one post from 2013 that's just a photo of my cat with no text at all (searching "cat" now correctly finds it, which I've decided is a feature). Pagefind lets you scope what gets indexed with a data-pagefind-body attribute so you're not indexing your nav bar and footer forever, and once I wrapped that around the actual post content instead of the whole page, results got a lot less noisy.

I also went and manually excluded about a dozen posts with data-pagefind-ignore. Not because they're bad exactly, just because they're the kind of post you write at 1am and regret slightly by breakfast, and I'd rather they not be the first thing someone finds when they search "javascript" and land on a 2012 rant instead of anything useful.

One thing I'll complain about: the default Pagefind UI is fine but looks like a browser extension from 2016, all default system fonts and a boxy input. I ended up ripping out most of their CSS and restyling it to match the rest of the site, which took longer than the actual search implementation. Worth it though — it finally looks like it belongs here instead of bolted on.

If your blog or personal site has been running long enough that you've forgotten what's actually in your own archive, I'd genuinely recommend doing this over a slow afternoon. I typed "raspberry pi" into my own search bar last week just to test it and found a post from January 2012 that I had completely forgotten writing, about trying to get one to run as a home media server on a class 4 SD card that took eleven minutes to boot. Eleven minutes. We've come a long way, apparently, and so has this blog's ability to remember what it's already said.