Adding Site Search to Eleventy with <pagefind-search>
I recently added some new ways to dig through the archives of this site, and chief among them was search. Search is something I’ve wanted to add for ages, and thankfully it was pretty straightforward, thanks to Pagefind and the <pagefind-search> web component by Zach Leatherman.
Installation
Installing the component starts with adding it to our package.json as a
regular dependency (not a DevDependency as you might be used to for
Eleventy):
npm install --save @zachleat/pagefind-search
With the package added, we need to add a reference to it into a JavaScript file
referenced in our site/page HTML. The specifics of how you do this will vary depending
on your setup/bundling process. For me, as I am using
Eleventy Excellent
for the basis of my site, the easiest way for me to do do this was to add an import to
the top of assets/scripts/app.js:
import '@zachleat/pagefind-search';
/* ... rest of existing file contents ... */
With that out of the way, we can add the component to which ever page or template you want to use it in. For me, this was my archive Nunjucks file, with the additions looking like so:
<h2>Search</h2>
<pagefind-search pagefind-autofocus>
<form action="https://duckduckgo.com/" method="get" style="min-height: 3.2em;"><!-- min-height to reduce CLS -->
<label>
Search for:
<input type="search" name="q" autocomplete="off" autofocus>
</label>
<!-- Put your searchable domain here -->
<input type="hidden" name="sites" value="chrismcleod.dev">
<button type="submit" class="button">Search</button>
</form>
</pagefind-search>
This is more-or-less a copy of Zach’s
example usage. The only difference is I’ve added the pagefind-autofocus attribute and
changed the fallback content to reference my domain. There are
other attributes
you can play with to customise the component further.
With these steps done, we’re nearly ready, but the search will not work until we generate the Pagefind bundle and index our site.
Indexing our site
Run your regular build command for your site, to generate the static files to be indexed. Indexing requires running another command:
npx -y pagefind --site dist
The --site option should point to the output directory of your build
process. For me, that’s the dist directory. Pagefind will go off and do
it’s thing, then report back what it’s indexed. The end of the output will look
something like this:
[Building search indexes]
Total:
Indexed 1 language
Indexed 520 pages
Indexed 10092 words
Indexed 0 filters
Indexed 0 sorts
Finished in 4.393 seconds
Everything should be generated now, you can go ahead and serve your site locally to try it out!
If you want to customise the Pagefind index, you should refer to the documentation. Personally, I’ve added a couple of attributes to my templates to indicate what should or shouldn’t be indexed.
This is all great so far, but we should automate things so the index is generated on
every build - especially if we use any sort of automated process to build and deploy
our site. To do this, you’ll need to modify whatever command you use to build your
site. For me, I modified the scripts section of my package.json to this
(I’ve omitted irrelevant lines, for clarity):
"scripts": {
"clean": "rimraf dist",
"build": "ELEVENTY_PRODUCTION=true run-s clean build:*",
"build:11ty": "eleventy",
"build:index": "npx -y pagefind --site dist",
},
The build entry is the one used by Netlify when it generates and deploys
my site from any updates checked into GitHub. The important bit that changed here,
from what it was before, is the addition of build:index. This adds the
step to the build process which indexes the site.
Customising the display (Optional)
Pagefind comes with its’ own UI CSS, which the <pagefind-search> component loads when initialising. The defaults are perfectly acceptable - they work well, and look good. They did unfortunately stick out a bit as “wrong” when I initially added the component, so I wanted to make some adjustments.
It took me a while to figure out how to get the results to display in a way that was more like the rest of my site - namely the typography and element spacing. It’s far from perfect, and I want to do some more work on this, but the following should help you customise things a bit to your liking.
The main issue you’ll face is the Pagefind stylesheet will probably load after your main site stylesheets - so if you, say, follow the documentation and override the styling using CSS variables, the default stylesheet will override this. Thankfully, we can use the cascade and specificity to make our overrides take effect.
As mentioned, I use Eleventy Excellent, which uses some build tools to bundle up
multiple CSS files into one. To keep things organised I added a new
search.css file in the src/assets/css/blocks directory (link). Your site will probably differ in the specifics - you should add any CSS in the
same way you normally would.
/* src/assets/css/blocks/search.css */
.pagefind-ui{
margin-block-start: var(--flow-space,1em);
--pagefind-ui-scale: 1.2;
--pagefind-ui-primary: var(--color-fg);
--pagefind-ui-text: var(--color-fg);
--pagefind-ui-background: var(--color-bg);
--pagefind-ui-border: var(--color-fg);
--pagefind-ui-tag: var(--color-primary);
--pagefind-ui-border-width: 2px;
--pagefind-ui-border-radius: var(--border-radius);
--pagefind-ui-image-border-radius: var(--border-radius);
--pagefind-ui-image-box-ratio: 3 / 2;
--pagefind-ui-font: var(--font-base);
}
.pagefind-ui__result {
--pagefind-ui-border: #eeeeee;
}
.pagefind-ui__button {
--pagefind-ui-primary: var(--color-fg);
--pagefind-ui-background: var(--color-bg);
margin-block-start: var(--space-s-m);
}
.pagefind-ui__button:hover {
--pagefind-ui-primary: var(--color-bg);
--pagefind-ui-background: var(--color-fg);
}
.pagefind-ui__search-input:focus-visible {
outline: 2px solid var(--color-primary);
}
The important bit to begin with is the .pagefind-ui rule declaration.
This is where we define the new generic values for the PageFind variables, and define
any properties which aren’t already set. If you want to override properties which
are set (and aren’t influenced by the variables), you’ll need to get more
into the weeds I’m afraid - which is outside the scope of this post. Maybe later, once
I’ve revisited this topic.
The reason this works is that the default PageFind variables are set on the
:root pseudo-element, which is less specific than
.pagefind-ui. In my case I set the values to variables set by Eleventy
Excellent’s own stylesheet, which lets the colours follow my implementation of Dark
Mode.
From here, it’s a case of finding the class for the right elements you want to modify,
and overriding the variables or adding new properties. For me, this was adjusting the
border between results, and making the “load more results” button more like the other
buttons on my site. One thing to remember: any variables you don’t override will use
the defaults - so if you’re happy with everything but the font, for example, you can
set the value of --pagefind-ui-font in .pagefind-ui, and
leave it at that.