The Setup: A Simple Task Before Work

Tomorrow I have to go to the office. Early. Way too early. So tonight, I thought I’d do something simple and “quick” with my blog.

You know, easy stuff:

  • Write a few blog posts
  • Update the projects list
  • Maybe organize something

Wait… there was that blog post I wrote about setting up multiple git users in different directories. And something about global gitignore. Which machine did I write that on? Let me search for it.

Oh. Right. I don’t have search on my blog.

Claude: Can confirm. We went from “let’s do something quick” to a 3-hour deep dive into static search implementations. Classic ADHD workflow.

The Realization: This is Actually a Problem

This wasn’t just about one missing blog post. This was about using my blog as a personal knowledge base.

I write about things I learn, problems I solve, commands I use. The whole point is that I can come back later and find them when I need them. Without search, it’s just a chronological archive that requires me to remember when I wrote something.

Which, if you have ADHD, is hilariously optimistic.

So: time to add search.

Option 1: The PaperMod Built-in (Fuse.js)

My blog uses the PaperMod theme for Hugo, which comes with built-in search using Fuse.js. Pretty standard setup:

  1. Generate a JSON index of all content
  2. Load it into the browser
  3. Client-side fuzzy search
  4. Done

I started implementing it. Got it working. Then I looked at the network tab.

520KB JSON file. For 52 blog posts.

That’s ~10KB per post, including the full content of every single post. Sure, it works, but that’s a lot to download just to search. And it’ll only grow as I write more.

Also, I noticed that one of my posts (the one in a page bundle with images) wasn’t being indexed properly. The content field was empty in the JSON. Not ideal when you’re trying to search for “git config” and it’s in that exact post.

Option 2: Let’s Try Pagefind

Claude suggested Pagefind. I’d heard of it but never tried it. The pitch:

  • Static search - No server needed
  • Build-time indexing - Scans your generated HTML
  • Chunked loading - Only downloads what you need
  • Built-in UI - Comes with a search interface

Sounded too good to be true. Let’s test it.

The Setup

# Build the site
hugo --environment production

# Run Pagefind indexer
npx pagefind --site public

That’s it. Seriously.

Output:

Running Pagefind v1.4.0
Indexed 220 pages
Indexed 6379 words
Finished in 0.376 seconds

Generated files:

  • Initial load: ~97KB (JS + CSS + entry point)
  • Total index: ~1.7MB (but chunked into 219 fragments)
  • Per-search load: ~4-20KB (only the relevant chunks)

Compare that to the 520KB upfront with Fuse.js. And this actually works with page bundles because it indexes the rendered HTML, not the markdown source.

The Implementation: Making it Pretty

Pagefind comes with a default UI, but I wanted it to match my PaperMod theme. Dark mode, proper colors, hover effects, the works.

Created a custom layout at layouts/_default/search-pagefind.html:

new PagefindUI({
    element: "#pagefind-search",
    showSubResults: true,
    excerptLength: 30,
    showImages: false
});

Then spent way too much time tweaking the CSS. The highlights were:

Problem 1: Dark mode made everything unreadable. Black text on dark background.

Solution: Force proper color variables with !important (yeah, I know, but Pagefind’s CSS is aggressive).

Problem 2: The search icon was invisible in dark mode.

Solution: SVG icon with proper stroke color and a media query for dark mode:

@media (prefers-color-scheme: dark) {
    .pagefind-ui__form::before {
        background-image: url("data:image/svg+xml,%3Csvg ... stroke='%23aaa' ...%3E");
        opacity: 0.8;
    }
}

Problem 3: Highlighted search results weren’t obvious enough.

Solution: Bright yellow (#ffeb3b) with black text and a subtle shadow. You can’t miss it now.

The Build Integration: Netlify Edition

My blog deploys via Netlify (push to GitHub → automatic build → live). So I needed to make sure Pagefind runs during the build.

Created netlify.toml:

[build]
  publish = "public"
  command = "hugo --environment production && npx pagefind --site public"

[build.environment]
  HUGO_VERSION = "0.150.0"
  NODE_VERSION = "20"

Now every push automatically:

  1. Builds the Hugo site
  2. Runs Pagefind indexer
  3. Deploys everything including the search index

Also added a make build command locally for testing:

build:
	@echo "Building Hugo site..."
	hugo --environment production
	@echo "Building Pagefind search index..."
	npx -y pagefind --site public
	@echo "✓ Done"

The Meta Discovery: Claude Session Images

While working on this, I kept pasting screenshots into the Claude chat to debug styling issues:

  • “The text is unreadable in dark mode”
  • “The Clear button is invisible”
  • “The highlights aren’t obvious enough”

Claude could see and analyze them, suggest CSS fixes, and we’d iterate.

But then I realized: these screenshots are stored somewhere. Where?

Found them in:

~/.claude/projects/<project-path>/<session-id>.jsonl

Example:

/Users/zoltantoma/.claude/projects/-Users-zoltantoma-Obsidian-MyNotes-zoltantoma/4c409127-5466-4208-b1b9-f5786f673350.jsonl

It’s JSONL (JSON Lines) format - one message per line. And the images are embedded as base64 encoded strings directly in the file.

Which means… I could theoretically:

  1. Parse the session file
  2. Extract the base64 images
  3. Decode and save them
  4. Use them in blog posts

Without having to re-screenshot everything manually.

Project idea saved to .tmp/claude-session-image-extractor.md

This is now on my “projects to build when I need a portfolio piece” list. The meta-irony of using it to write about itself is too good to pass up.

The Results

Search is live. Works great. Tested it with “git config” and found the post immediately.

Performance:

  • Initial page load: ~97KB
  • Search for “git”: ~15KB additional (loads 2-3 chunks)
  • Total network transfer: ~112KB
  • Compare to Fuse.js: 520KB upfront

User experience:

  • Real-time search as you type
  • Highlights search terms in bright yellow
  • Shows context snippets (not just titles)
  • Keyboard navigation works (arrow keys, Enter)
  • Mobile-friendly

Developer experience:

  • Zero config (just run the indexer)
  • No manual JSON maintenance
  • Works with page bundles automatically
  • Deploys seamlessly on Netlify

The Punchline

So… where is that git config blog post?

Searched for it. Found it instantly. It’s in “Setting Up My Hugo Blog: From Tilt.dev to… Just Hugo” - the post about my development environment setup.

Turns out I never actually wrote a dedicated post about git config and global gitignore.

I thought I did. I remember writing it. But it doesn’t exist.

Classic.

At least now when I do write it, I’ll be able to find it. 😄

What’s Next

The search works. The blog is searchable. The project idea is documented.

Now I should probably actually write that git config post.

Or go to bed. I have to wake up early tomorrow.

One of those.


Tech stack:

  • Hugo 0.150.0
  • Pagefind 1.4.0
  • PaperMod theme
  • Netlify deployment
  • ~220 pages indexed in 0.376 seconds

Files changed:

  • netlify.toml - Build configuration
  • layouts/_default/search-pagefind.html - Custom search UI
  • config/_default/config.toml - Menu and outputs
  • Makefile - Added make build command
  • static/css/custom.css - Search styling

Lines of CSS written while debugging dark mode: Too many.

Times I said “this is a quick task”: Once. At the beginning. Never again.