Me. In case I want to retrace the big idea tomorrow

tldr: https://janusworx.com/search
Being the lazyass that I am, I used to depend on search engine indexes to find something on my own blog. (First google, then DuckDuckGo.)
Kushal, having https://search.kushaldas.in/ has always made me jealous though.
And now, the days of finding something relevant on my blog, on the first search are long gone. Search engines today are a pale shadow of what they used to be. The current load of crap actually makes me long for the days of Altavista!
So, I decided to get off my butt, and go do something about it, because everytime I want something, my blog is one of the first places I search.
For e.g. when writing this post, I needed to look up how to add line numbers to my code blocks. Search helped me find it :)
I went to the Hugo website and decided to implement the first search tool1 they suggested: Pagefind.
Well, I did that, since it took care of the only thing I’m anal about.
Everything is local and under my control. If not I’d have kept looking for something else.
What it does, is parses the site, Hugo builds for me, and creates a search index along with some more goodies (an api for search as well as UI components) in a bundle. The rendered html and the bundle together are now the artifact that my site is made up of.
The Plan
- Get Pagefind and install it into a tools subdirectory in my blog’s directory, so I always have it handy (and the Forgejo action can get its grubby paws on it when it wants to run the publishing workflow)
- Create an entry in my menu, so I can go clicky to a search page.
- Create said search page.
- Make search work.
- Make the page resemble the rest of the blog as much as possible.
Installing it and wiring it all up
1. Installation
This is the easy part. Pagefind is a single binary, which I shoved into the tools subdirectory.
2. Creating an entry for search in the navigation menu
I edited my Hugo config (mine is called hugo.yaml), and added search as a menu item.
More shifted downwards, as did its weight.
menus:
main: # elided config
- identifier: search
name: search
url: /search
weight: 60
- identifier: more
name: more …
url: /more/
weight: 70
3. Creating the search page.
This was a two step process.
a. I needed a placeholder page that I could render some layout (a template) into.
This was search.md which lives in my content directory.
This is all it contains …
| |
See the key that says layout on line 3? This is looking for a layout called search which is what we now create …
b. Creating the search layout.
Papermod already ships with a layout called search. Which I did not want to use because it is meant for use with another local search engine called lunr.js.
Hugo lets you override whatever your theme provides, by letting you create your own versions of said file in the layouts directory. Hugo will then look at that instead of the theme’s file.
Over the years I’ve gotten dangerous enough by using Hugo’s lovely channel for discourse. Between that, and Hugo’s documentation as well as Pagefind’s own help page, I rustled up a layout file called search.html which lives in my layouts/_default directory. This is what it contains …
{{ define "main" }}
<link rel="stylesheet" href="/pagefind/pagefind-component-ui.css">
<script type="module" src="/pagefind/pagefind-component-ui.js"></script>
<div class="main">
<article class="post-single">
<header class="post-header">
<h1 class="post-title">{{ .Title }}</h1>
</header>
<div class="post-content search-content">
<pagefind-config></pagefind-config>
<pagefind-input placeholder="Search for something …"></pagefind-input>
<pagefind-summary></pagefind-summary>
<pagefind-results></pagefind-results>
</div>
</article>
</div>
{{ end }}
This creates the box on the search page, ready to take input and show results.
4. Make the page resemble the rest of the blog as much as possible.
Doing things a bit out of order and finishing up with the page first.
Leaned on Pagefind’s docs as well as Chatgpt, Claude and Gemini for this part to create a CSS file, that looks pretty native to my blog. I’ve called it include-pagefind.css and it lives in the assets/css/extended subdirectory.
| |
5. Make Search Work.
This is a 3 step process
a. Build the blog.
Easy peasy. hugo build and we have our blog rendered in the public subdirectory.
b. Get Pagefind to create its index in the build directory.
This is easy as well.
./tools/pagefind --site public
and Pagefind creates a pagefind subdirectory in our public directory where it creates its search index after reading all the html.
All I need to do now, is just …
c. Publish!
Ok, I lied a teensy bit. I don’t have to do steps a. and b. because they are part of step c.
The Forgejo action checks out my committed files, pulls in Hugo from a private package repo, builds the site, then runs Pagefind to generate the index2 and finally uses rysnc to sync it with the VM. Here’s the relevant section of the action.
# lots of stuff elided …
jobs:
build-and-deploy:
runs-on: my-big-box
env:
HUGO_VERSION: ${{ vars.HUGO_VERSION }}
steps: # lots of snipped stuff …
- name: Checkout the website repo
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 1
- name: Build website with Hugo
env:
HUGO_ENVIRONMENT: production
run: |
hugo \
--gc \
--minify
- name: Build the Pagefind search index
run: |
tools/pagefind \
--site public
I thought the indexing would take a lot of time and was hesitant to use it as part of a regularly run Forgejo action workflow, but several runs show that the index generation only increases the time by at a couple of seconds at most. This is amazing!
Now I can search my own words, on my own blog, to my hearts content!
Feedback on this post?
Mail me at feedback at this domain.
P.S. Subscribe to my mailing list!