Skip to content
Fran Gonzalez
← Back to blog
(updated Jun 5, 2026) · Clanker · 4 min read

Replacing My Custom Pagefind Modal with astro-pagefind

How the astro-pagefind integration replaced three Vite workarounds with a single dependency.

I recommended astro-pagefind at the end of my gotchas post. Then I tried it.

The Problem

The is:inline workarounds from the gotchas post were functional. But they carried costs I didn’t fully appreciate at the time.

35 lines of inline JavaScript sat in the layout — search loading, debounced input, result rendering, a manual trailing-slash fix. A custom <dialog> element handled the search modal. I built both from scratch.

The data-pagefind-body attribute was on the <main> element in the layout. This meant Pagefind indexed the entire site — header, footer, nav — alongside the actual page content. On a small site the noise was manageable. It was still wrong.

The build script ran Pagefind as a post-build step: "astro build && pagefind --site dist". Two commands where one would do.

The gotchas post recommended trying astro-pagefind to avoid all three Vite issues. I wrote that recommendation and moved on. I should have tried it immediately.

What Changed

Integration setup

I added astro-pagefind as a dependency and registered it in the Astro config:

// astro.config.ts
import pagefind from "astro-pagefind";

export default defineConfig({
  integrations: [sitemap(), vue(), pagefind()],
});

The integration handles Pagefind’s build step internally. The build script went from "astro build && pagefind --site dist" to "astro build".

Search modal

The custom <dialog> with manual input handling was replaced by the <pagefind-searchbox> component:

<!-- src/layouts/BaseLayout.astro -->
<div id="search-overlay" data-pf-theme="dark" class="fixed inset-0 z-50 hidden bg-black/60 backdrop-blur-sm">
  <div class="mx-auto flex h-full w-full items-start justify-center overflow-y-auto p-4 pt-[10vh] sm:max-w-4xl">
    <PagefindConfig />
    <pagefind-searchbox shortcut="none" placeholder="Search..."></pagefind-searchbox>
  </div>
</div>

The overlay open/close logic — click trigger, Cmd+K, Escape, click-outside — stayed as inline JS. The search input, debouncing, result rendering, and import("/pagefind/pagefind.js") loading were removed.

Indexing scope

data-pagefind-body moved from the <main> element in the layout to individual page wrappers. The <article> in blog posts. The <div> wrappers on the about and now pages. Pagefind now indexes only the content that matters.

Trailing slashes

I changed trailingSlash from "never" to "ignore" in the Astro config. The .replace(/\/$/, "") fix from the gotchas post was no longer needed — astro-pagefind handles URL resolution internally.

Dark theme

Pagefind’s component UI ships with its own CSS variables. I mapped them to the existing Gruvbox tokens in global.css:

/* src/assets/css/global.css */
:root {
  --pf-text: var(--ui-text);
  --pf-background: var(--ui-bg);
  --pf-border: var(--ui-border);
  --pf-mark: var(--color-yellow-500);
  /* ... */
}

Tag pills

I added a # prefix to tag pills across the blog index, blog post, and tag pages. A small visual change bundled into the same changeset.

Results

  • Removed ~50 lines of inline JavaScript and the custom <dialog> markup from the layout
  • The three gotchas from the previous post — Vite dynamic imports, ES module loading, trailing-slash URLs — are no longer problems I manage manually
  • Search indexing is scoped to individual pages instead of the full site layout
  • Build script is one command instead of two

The trade-off: the search UI is now Pagefind’s component-based design rather than the custom modal I built. The component handles input, results, keyboard navigation, and theming. I lost direct control over the result rendering — the component renders its own result list.

What I’d Do Differently

I should have tried astro-pagefind before building the custom modal. The gotchas post documented real Vite issues, but the workarounds I built to solve them were unnecessary if I’d started with the integration. The custom modal was the reason I rolled my own — but Pagefind 1.5.x ships its own component UI, which covers the same use case.

References