Implementing search on this site
I decided to add a search function to this blog, but this time I had to accept some browser side JavaScript for it to function. This entry will describe how I implemented this (using pagefind) on this site.
Table of Contents
To make this work, we will essentially:
- Build hugo
- Run pagefind on this output (
pagefind --site public) - Deploy the static site
For testing I’ve just downloaded a prebuilt binary and ran it on my laptop, but for CI/CD we will…do the same thing, but in a container.
Some light testing gives that loading the search view will consume ~100 KB of bandwidth, and searching will consume 100-200 KB more. I know this isn’t a lot these days, and that bandwidth et c is cheap, but I still take some silly pride in keeping this site lean.
Hugo
Search bits
First up, the list view for searching:
{{ define "main" }}
<h1>Search</h1>
<p>This search <strong>requires</strong> JavaScript, and will consume several hundreds of KBs of bandwidth while in use!</p>
<p />
<div data-pagefind-ignore id="search"></div>
<link href="/pagefind/pagefind-ui.css" rel="stylesheet">
<script src="/pagefind/pagefind-ui.js"></script>
<script>
window.addEventListener("DOMContentLoaded", () => {
new PagefindUI({
element: "#search",
showSubResults: true,
resetStyles: true,
pageSize: 10,
showImages: false,
autofocus: true,
sort: {
date: "desc"
}
});
});
</script>
{{ end }}
Then, to make sure this is actually rendered by hugo, we need to add
an empty page in the right place. I saved mine as
content/search/_index.md and it just contains:
---
title: "Search"
date: 2026-01-26T12:48:48+01:00
url: "/search/"
outputs: ["HTML"]
---
Adding more metadata
This was the tricky part – which metadata to include in the search results?
I went with the following:
- Indexing:
- Title
- Date
- Tags
- Series
- Ignoring:
- Taxonomy lists
- Term lists
- Home page
This section is a bit hand-wavy as it’s very specific for my blog, but adding them went something like this:
<!-- Part of my baseof.html -->
{{- $indexable := true }}
{{- if in (slice "home" "taxonomy" "term") .Kind }}
{{ $indexable = false }}
{{- end }}
{{- if eq (.Params.pagefind | default true) false }}
{{ $indexable = false }}
{{- end }}
{{- if eq .Section "search" }}
{{ $indexable = false }}
{{- end }}
{{ if and (eq .Kind "section") (eq .Section "posts") }}
{{ $indexable = false }}
{{ end }}
<main {{ if $indexable }}
data-pagefind-body
{{ else }}
data-pagefind-ignore
{{ end }}>
{{ block "main" . }}{{ end }}
</main>
For my default single.html:
{{ partial "chroma-theme.html" . }}
<article>
<header>
- <h1>{{ .Title }}</h1>
+ <h1 data-pagefind-meta="title">{{ .Title }}</h1>
<p>
- <time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "January 2, 2006" }}</time>
+ <time data-pagefind-sort="date:{{ .Date.Format "2006-01-02" }}" datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "January 2, 2006" }}</time>
{{- with .Params.tags }}
- <span class="tags">
+ <span class="tags" data-pagefind-meta="tags:{{ delimit . "," }}">
{{ range . }}
<a class="tag" href="{{ "/tags/" | relURL }}{{ . | urlize }}">{{ . }}</a>
{{ end }}
For my series snippet:
{{- if .Params.series -}}
- <div class="series-info">
+ <div class="series-info" data-pagefind-meta="series:{{ .LinkTitle }}">
{{- with index (.GetTerms "series") 0 -}}
<p>This post is part of the <b>{{ .LinkTitle }}</b> series.</p>
{{- end -}}
{{- $series := where .Site.RegularPages.ByDate ".Params.series" "intersect" .Params.series -}}
{{- with $series -}}
<p>
You get the idea!
Woodpecker
This will just add an intermediate build step before publishing this, below is a partial for my PR workflow (which just publishes to an internal staging environment):
when:
- event: pull_request
steps:
# skipping step mermaidcharts due to length...
build:
image: hugomods/hugo:go-git-0.150.1
depends_on:
- mermaidcharts
commands:
- hugo --baseURL=$INTERNAL_URL --minify --buildDrafts --buildFuture
environment:
INTERNAL_URL:
from_secret: internal-url
pagefind:
image: alpine:latest
depends_on:
- build
commands:
- apk add wget
- wget --quiet https://github.com/Pagefind/pagefind/releases/download/v1.4.0/pagefind-v1.4.0-x86_64-unknown-linux-musl.tar.gz -O /tmp/pagefind.tar.gz
- tar zxf /tmp/pagefind.tar.gz -C /tmp
- /tmp/pagefind
# skipping the publishing step...
…and Bob is your mothers brother!