Ben Oliver
|
|
17 March 2025

Adding suggestions to a 404 page with pagefind

Banner image for Adding suggestions to a 404 page with pagefind

So I’ve gone into how I implemented search1 on this site, now I want to explain how I’m also using pagefind2 to dig up article suggestions on the 404 page (i.e. when someone visits a URL on this site that doesn’t exist). This isn’t super specific to Jekyll I don’t think, it should work anywhere you are using pagefind.

First of all I must seek forgiveness from the internet gods - over the years my URLs have changed and links have broken over time. I set up redirects but maintaining a huge list of them just isn’t something I can keep on top of forever. Plus if I changed webserver I have to port all the redirects over. Yes I am aware this makes me a bad person.

There is the next-best-thing though which is what we are doing here - using our little search engine to give suggestions of possible correct pages.

This is actually something that’s nice to do regardless of my URL changing sins - it helps people out who might have made a typo in the URL (people type URLs out manually… right? Anyone?).

Include the page slug in the search index

In this context the ‘slug’ is the bit at the end of the URL that sort of mimics the title of the article. e.g. on this page the slug is 404search. It’s important that we can search using this slug, but unless you write it in every post, pagefind doesn’t know what it is by default.

So the first step is to add the slug to every post, and hide it. In jekyll it’s

<div class="hidden">404search</div>

Where ‘hidden’ is a class I have set up that hides the div using css. Make sure it is inside an element that is inside your pagefind indexed stuff, like <main data-pagefind-body>. Or you can just add data-pagefind-body to the hidden div and it will add it to the index along with your main content.

Now you can search for posts using the slug, as well as normal search terms. Handy!

Set up your 404 page

I’m just gonna dump the code for my 404 page at the bottom of this article but in short we are going to use some javascript now to:

  1. Take the slug from the URL
  2. Send it to pagefind using their API
  3. Render a list of search results that include the slug
  4. If there is only one result, redirect to that page

The hit-rate for this seems to be pretty good!

Some notes and caveats

The 404 page code

---
layout: page
permalink: /404
title: "Error 404"
---

<div id="suggestions" class="hidden">
  <h2>You might have been looking for:</h2>
</div>
<script>
    document.addEventListener('DOMContentLoaded', async () => {
      const pagefind = await import('/pagefind/pagefind.js');
      await pagefind.init(); // Initialize Pagefind
      suggestPages(pagefind); // Call the suggestion function
    });
    async function suggestPages(pagefind) {
      // Extract the page slug from the pathname
      const pathSegments = window.location.pathname.split('/');
      const slug = pathSegments.filter(segment => segment.trim() !== '').pop() || '';
      // Perform the search using the slug as the search term
      const search = await pagefind.search(slug);
      // Load data for a subset of results
      const maxSuggestions = 5;
      const results = await Promise.all(search.results.slice(0, maxSuggestions).map(r => r.data()));
      if (results.length === 1) {
        window.location.href = results[0].meta && results[0].meta['url'] ? results[0].meta['url'] : results[0].url;
      } else {
        displaySuggestions(results);
      }
    }
    function displaySuggestions(results) {
      const suggestionContainer = document.getElementById('suggestions');
      // Remove the 'hidden' class to allow visibility
      suggestionContainer.classList.remove('hidden');
      if (results.length === 0) {
        suggestionContainer.innerHTML = '<p>No suggestions available.</p>';
        return;
      }
      suggestionContainer.innerHTML += ''; // Clear previous content
      results.forEach(result => {
        // Use metadata if available
        const url = result.meta && result.meta['url'] ? result.meta['url'] : result.url;
        const title = result.meta && result.meta['title'] ? result.meta['title'] : 'Untitled';
        const date = result.meta && result.meta['date'] ? result.meta['date'] : 'No date available';
        const suggestion = document.createElement('div');
        suggestion.innerHTML = `
          <h3><a href="${url}" data-pagefind-meta="url[href]">${title}</a></h3>
          <p>${result.excerpt}</p>
          <p class="small-caps lowercase">${date}</p>
        `;
        suggestionContainer.appendChild(suggestion);
      });
    }
  </script>
Reply by email