Implementing Blog Search Using Pagefind

profile image

I quickly added search functionality to my blog using Pagefind, a client-side search library for static websites.

This post has been translated by Jetbrains's Coding Agent Junie junie logoPlease let me know if there are any mistranslations!

Pagefind is a client-side search library designed for static websites. It allows you to add powerful search functionality to websites built with static site generators (SSG) or frameworks like Next.js, Gatsby, Hugo, Jekyll, etc. Since it can be installed and configured locally without external APIs or service keys, it can be applied very quickly and easily.

How It Works

Pagefind operates through the following process:

  1. Indexing Phase: After building the site, it analyzes HTML files to generate a search index. During this phase, text content, titles, metadata, etc. are extracted and processed, and a js file is created that allows the indexed data to be used. This means it can only be used when HTML is generated during the build process.
  2. Search API: After index generation, you can implement a search interface on your site using the provided JavaScript API.
  3. UI Generation: When a user enters a search term, Pagefind quickly finds relevant pages and sections using the pre-generated index and provides results, and we only need to implement the UI using these results.

Various features and usage instructions are available in the Docs.

Getting Started with Pagefind | Pagefind — Static low-bandwidth search at scale

Quick Start!

Writing the Script

Let's apply it right away without further ado. You don't need to install any packages, just follow the instructions below. My development environment uses Next.js 15 and pnpm.

First, add postbuild to your package.json.

json
"scripts": {
  // ...
  "postbuild": "npx pagefind --site .next --output-path public/pagefind",
  // ...
},

Keep in mind that for indexing to work, HTML must be generated, and for HTML to be generated, Next must be built. After adding this script and building, you can see pagefind-related files appear under the public folder.

Image.png

Now you just need to import and use the pagefind.js file that was generated here.

API Call

Here's the complete code:

typescript
export default function Search() {
  const [search, setSearch] = useState("");
  const [results, setResults] = useState<PagefindResult[]>([]);
  const [pagefind, setPagefind] = useState<any>(null);

  useEffect(() => {
    const initPagefind = async () => {
      try {
        // Attempt to load dynamically at runtime

        setPagefind(
          await import(
            // @ts-expect-error
            "./pagefind/pagefind.js"
          ),
        );
      } catch (error) {
        console.error("Pagefind initialization failed:", error);
      }
    };

    // Execute only on client side
    if (typeof window !== "undefined") {
      initPagefind();
    }
  }, []);

  const handleSearch = async (e: any) => {
    setSearch(e.target.value);
    if (!pagefind || e.target.value === "") {
      setResults([]);
      return;
    }

    const search = await pagefind.search(e.target.value);
    const results = await Promise.all(search.results.map((r) => r.data()));
    console.log(results);
    setResults(results);
  };

  return (
    <div>
      <input
        type="text"
        value={search}
        onChange={handleSearch}
        placeholder="Enter search term..."
      />

      <div>
        {results.map((result, i) => (
          <div key={result.url}>
            <a href={result.url}>{result.meta.title}</a>
          </div>
        ))}
      </div>
    </div>
  );
}

UI Generation

Now that we've made the call, let's look at the results.

Image.png

The main items you can examine are as follows:

  • excerpt: This is a partial parsing of the content that contains the keyword.
  • meta: This retrieves meta information from the content. For title, it's the first h1 tag encountered, and for image, it's the first image tag encountered after the h1 tag.

All of these can be customized through additional options, so check the Docs carefully!

Problem Encountered

My blog provides content in four languages: Korean, English, Chinese, and Japanese, but when searching, posts in all languages were appearing.

Image.png

Since multilingual pages are a common case, Pagefind naturally provides a Multilingual search feature. However, it determines this based on what value is in the lang attribute of the html tag.

But in Next.js, this setting needs to be done in the Layout, and as far as I could find, there was no way to get the lang value during static page building... (Please let me know if there is a way)

Solution

So I used Pagefind's filter feature as a workaround. I added the following additional attributes to the h1 tag on each page:

html
<h1
  data-pagefind-filter="lang[data-lang]"
  data-lang={params.lang}
  className="text-3xl md:text-5xl font-bold"
>
  {title}
</h1>

And then added filters when making API requests:

typescript
const search = await pagefind.search(e.target.value, {
  filters: {
    lang: "ko",
  },
});

Conclusion

I quickly implemented search functionality using the small and lightweight library Pagefind. Since it generates search indexes independently without relying on external services, it has advantages in terms of privacy and control, and also provides fast response times from a user experience perspective, making it very useful for blogs or documentation sites.

❤️ 0
🔥 0
😎 0
⭐️ 0
🆒 0