Añadiendo un buscador al blog con Pagefind

Si llevas tiempo siguiendo el blog sabrás que soy bastante reacio a añadir cosas que no sean estrictamente necesarias. Sin comentarios, sin rastreadores gordos, sin JavaScript a menos que no haya otra opción. Pero hay cosas que, cuando te
las piden dos veces desde sitios distintos, es mejor escuchar.

Primero fue una IA analizando la estructura del blog en la nota Lo que ve una IA en el blog. Luego fue @[email protected] preguntándome por qué no había ninguna forma de buscar dentro del sitio.

Qué opciones tenía

Antes de lanzarme a instalar lo primero que encontrara, estuve mirando qué alternativas existen para un blog estático como este:

  • Un formulario que redirige a DuckDuckGo con site:elblogdelazaro.org.  Cero JavaScript, cero dependencias. Pero los resultados se ven en DuckDuckGo,  no dentro del blog, y dependes de que DuckDuckGo haya indexado bien tu sitio.
  • Fuse.js o Lunr.js, que son librerías JavaScript que buscan dentro de un  fichero JSON con todo el contenido del sitio. Pero ese JSON puede  pesar varios cientos de kilobytes si tienes muchas publicaciones.
  • Pagefind, que genera un índice estático a partir del HTML ya compilado,  lo sirve desde tu propio servidor y el bundle de JavaScript que carga en el  navegador es muy pequeño.

Me quedé con Pagefind. Es lo más ligero de las opciones con JavaScript, no depende de terceros y encaja bien con el flujo de Hugo.

Cómo funciona Pagefind

La idea es sencilla: primero Hugo genera el sitio estático en la carpeta public/, y luego Pagefind analiza ese HTML generado y crea su propio índice de búsqueda dentro de public/pagefind/. Ese índice y el JavaScript necesario para usarlo se sirven desde tu propio servidor, sin llamadas externas.

El flujo de build queda así:

hugo --gc --minify
./pagefind --site public

Primero Hugo, luego Pagefind.

Instalación

Pagefind se puede usar de varias formas. Yo descargué directamente el binario para Linux desde su página de releases en GitHub, lo descomprimí y lo dejé en la raíz del proyecto.

También se puede usar con npx si tienes Node instalado:

npx -y pagefind --site public

O incluso con Python:

python3 -m pip install 'pagefind[bin]'
python3 -m pagefind --site public

Lo que te sea más cómodo según lo que tengas en el sistema.

Preparando Hugo

Para que Pagefind no indexe todo lo que encuentra (menús, footers, listas de etiquetas…) hay que indicarle dónde está el contenido principal. Se hace con un atributo en el HTML: data-pagefind-body.

En mi caso lo añadí en baseof.html, que es la plantilla base de la que heredan todas las páginas:

<main id="content" data-pagefind-body>
 {{ block "main" . }}{{ end }}
</main>

Con eso Pagefind sabe que solo tiene que indexar lo que esté dentro de ese <main>, ignorando cabecera, pie y navegación.

Creando la página de búsqueda

La página de búsqueda es simplemente una página más del blog, en content/pages/search.md, con este frontmatter:

---
title: "Buscar"
type: "pages"
layout: "search"
---

Y su propio layout en layouts/pages/search.html, donde se carga la UI de Pagefind:

{{ define "main" }}
<main class="article-container">
 <hr />
 <h1 style="text-align:center;">{{ .Title }}</h1>
 <section class="article" style="padding-top: 0;">
   <link rel="stylesheet" href="/pagefind/pagefind-ui.css">
   <script src="/pagefind/pagefind-ui.js"></script>
   <div id="search"></div>
   <script>
     window.addEventListener("DOMContentLoaded", () => {
       new PagefindUI({
         element: "#search",
         bundlePath: "/pagefind/",
         showSubResults: true,
         excerptLength: 30
       });
     });
   </script>
 </section>
</main>
{{ end }}

El CSS

La UI de Pagefind tiene su propio estilo, pero se puede personalizar sobreescribiendo sus variables CSS. Le pasé la paleta del blog (colores, fuente monospace, bordes, modo oscuro) para que encajara con el resto del sitio sin descuadrar nada.

Añadir la búsqueda al menú

En el config.toml, dentro de [menu], añadí una entrada más al menú principal:

[[menu.main]]
     identifier = "search"
     name = "🔍Buscar"
     url = "/pages/search/"
     weight = 5

Los problemas que me encontré

El layout no se aplicaba. La página mostraba el título pero no la caja de búsqueda. El problema era que el frontmatter no tenía layout: "search", así que Hugo usaba el layout genérico de páginas en lugar del específico de búsqueda.

El HTML del Markdown no se renderizaba. Intenté meter el <div id="search"> y los scripts directamente en el .md, pero como tengo unsafe = false en la configuración de Goldmark, Hugo limpia el HTML embebido en el contenido. La solución fue mover todo al layout, no al Markdown.

Aparecían páginas de listados en los resultados. Al buscar cualquier cosa aparecían resultados titulados “Artículos” o “Notas” que apuntaban a las páginas de sección, no a publicaciones individuales. Para excluirlas, copié el layout de lista del theme a mi carpeta local y añadí data-pagefind-ignore en el <main>:

cp themes/minindie/layouts/_default/list.html layouts/_default/list.html

Y en el fichero:

<main class="content-list" data-pagefind-ignore>

El resultado

El buscador funciona. Puedes probarlo en /buscar/: escribe cualquier palabra y te aparecen resultados de artículos, notas y páginas con el fragmento de texto donde aparece esa palabra.

Es JavaScript pero solo se carga cuando entras a la página de búsqueda, no en el resto del sitio. El blog sigue siendo igual de ligero en todas las demás páginas.

Espero que te haya gustado, pasa un buen día. 🐧

Comentarios en Mastodon

Cargando respuestas…

Responder en Mastodon