Adding a Simple Search to an Astro Blog
I’ll show you how to add a search like the one on my site to your own Astro project.
We’ll use a package called PageFind to index our Astro site and provide an instant fuzzy search.
No third party services. No complex setup. Totally free.
This should work no matter where your data comes from - Markdown, MDX, or a CMS like Prismic. Best of all, it auto-updates on every page build, so you can set it and forget it.
We’re going to create a /search
page, mainly so that we don’t need to worry about styling modals or anything else. It’ll look something like this when we’re done.
We’re going to use the astro-pagefind package with a bit of extra customization.
This tutorial assumes you already have an Astro site with content to search for.
Installing and configuring PageFind
To get started you’ll want to install both astro-pagefind
and pagefind
as we’ll reference that directly in our custom search component.
npm i astro-pagefind pagefind
Then add the integration to the integrations array in your astro.config
file.
//astro.config.ts
import { defineConfig } from "astro/config";
import pagefind from "astro-pagefind";
export default defineConfig({
integrations: [
...,
pagefind()
],
});
I put it at the end of the array, as I think that makes it run last.
When Astro builds, this integration indexes the pages that were created. It generates that search index as static files for lightning fast access client-side.
We need to build our site once locally to generate those files. If you don’t build you won’t get any results from localhost.
npm run build
Then you can start your dev server.
npm run dev
Creating the search page
Create a file at src/pages/
called search.astro
. Bring over your layout and whatever else you are using across the other pages of your website. Here’s an example structure, but yours will look different depending on your setup.
// src/pages/search.astro
import BaseLayout from "@layouts/BaseLayout.astro"; const title = "Search | Alex
Trost"; const description = "Search the website of Alex Trost";
<BaseLayout title={title} description={description}>
<h1>Search</h1>
<!-- Search field will go here -->
</BaseLayout>
If you visit http://localhost:3000/search you should see your layout with your “Search” heading.
Now let’s create the search component itself.
Adding the component
In your components/
directory, create a file named SearchField.astro
.
This component handles the text input field and the rendering of the results.
// src/components/SearchField.astro
import "@pagefind/default-ui/css/ui.css"; import { join } from "node:path";
interface Props { readonly id?: string; readonly className?: string; } const {
id, className } = Astro.props as Props; const bundlePath =
join(import.meta.env.BASE_URL, "pagefind/"); const divProps = { ...(id ? { id }
: {}), ...(className ? { class: className } : {}), };
<div {...divProps} data-pagefind-ui data-bundle-path={bundlePath}></div>
<script>
import { PagefindUI } from "@pagefind/default-ui";
window.addEventListener("DOMContentLoaded", () => {
const allSelector = "[data-pagefind-ui]";
for (const el of document.querySelectorAll(allSelector)) {
const elSelector = [
...(el.id ? [`#${el.id}`] : []),
...[...el.classList.values()].map((c) => `.${c}`),
allSelector,
].join("");
const bundlePath = el.getAttribute("data-bundle-path");
new PagefindUI({
element: elSelector,
bundlePath,
showImages: false,
debounceTimeoutMs: 100,
});
const input = el.querySelector<HTMLInputElement>(`input[type="text"]`);
input?.focus();
// Check if the current URL has any query params
const url = new URL(window.location.href);
const params = new URLSearchParams(url.search);
const query = params.get("q");
// If query exists on page load
if (query && input) {
input.value = query;
input.dispatchEvent(new Event("input", { bubbles: true }));
}
// Add Listener to update the URL when the input changes
input?.addEventListener("input", (e) => {
const input = e.target as HTMLInputElement;
const url = new URL(window.location.href);
const params = new URLSearchParams(url.search);
params.set("q", input.value);
window.history.replaceState({}, "", `${url.pathname}?${params}`);
});
}
});
</script>
I copied the Search component out of astro-pagefind
and customized it with a few extra features.
First I made the input get the page focus on load. That’s what input?.focus()
does for us.
Next I wanted to handle query parameters so that I could link to search results like this: https://trost.codes/search?q=writing
. I added comments to the code where those changes were made.
I also changed the properties that PageFindUI
gets instantiated with.
new PagefindUI({
element: elSelector,
bundlePath,
showImages: false,
debounceTimeoutMs: 100,
});
Don’t change element
and bundlePath
as those are necessary for it to work. But if you want PageFind to pull in images from your pages, you can set showImages
to true. Only some of my content has images, so it was mostly empty space for me. You can find the other properties in the PageFindUI docs.
The rest of the component comes from the astro-pagefind
package.
Now add the component to your search page.
//src/pages/search.astro
import BaseLayout from "@layouts/BaseLayout.astro"; import SearchField from
"@components/SearchField.astro"; const title = "Search | Alex Trost"; const
description = "Search the website of Alex Trost";
<BaseLayout title={title} description={description}>
<h1>Search</h1>
<SearchField />
</BaseLayout>
Save the file, and you should see the search box on your page.
Styling
Luckily they’re using CSS custom properties for styles, so we can easily add our own values. Check the docs on customizing the styles for more details.
Here are the default values.
:root {
--pagefind-ui-scale: 1;
--pagefind-ui-primary: #034ad8;
--pagefind-ui-text: #393939;
--pagefind-ui-background: #ffffff;
--pagefind-ui-border: #eeeeee;
--pagefind-ui-tag: #eeeeee;
--pagefind-ui-border-width: 2px;
--pagefind-ui-border-radius: 8px;
--pagefind-ui-image-border-radius: 8px;
--pagefind-ui-image-box-ratio: 3 / 2;
--pagefind-ui-font: sans-serif;
}
I changed out most of the colors for my own CSS custom properties so they change with my site’s dark mode.
If you want to have a completely unstyled search input you can remove the css import at the top of SearchField.astro
.
Now deploy your site and it’ll run that build script and index your content.
Wrap up
While services like Algolia and ElasticSearch are super powerful, I like these self-hosted, static solutions for smaller projects.
Is there any additional functionality you want to add to that Search component? Any ways to improve it? I’d love to hear about it.
For more information about the PageFind package, check out the Pagefind docs.