﻿﻿{"id":1947,"date":"2025-12-31T12:00:38","date_gmt":"2025-12-31T11:00:38","guid":{"rendered":"https:\/\/elearningsamba.com\/index.php\/comment-jai-vire-algolia-et-recree-le-google-de-1998-sur-mon-site\/"},"modified":"2025-12-31T12:00:38","modified_gmt":"2025-12-31T11:00:38","slug":"comment-jai-vire-algolia-et-recree-le-google-de-1998-sur-mon-site","status":"publish","type":"page","link":"https:\/\/elearningsamba.com\/index.php\/comment-jai-vire-algolia-et-recree-le-google-de-1998-sur-mon-site\/","title":{"rendered":"Comment j&#8217;ai vir\u00e9 Algolia et recr\u00e9\u00e9 le Google de 1998 sur mon site"},"content":{"rendered":"<p>Bon, faut qu&#8217;on parle un peu du moteur de recherche de mon site. Ceux qui l&#8217;ont d\u00e9j\u00e0 utilis\u00e9 savent de quoi je parle : <strong>c&#8217;\u00e9tait pas terrible<\/strong>. Enfin, \u00ab\u00a0pas terrible\u00a0\u00bb j&#8217;suis gentil. C&#8217;est un esp\u00e8ce d&#8217;overlay avec des r\u00e9sultats certes fiables mais c&#8217;\u00e9tait vraiment pas pratique.<\/p>\n<p>Et en plus de \u00e7a, comme j&#8217;ai un site statique g\u00e9n\u00e9r\u00e9 avec Hugo, je passais par <strong>Algolia<\/strong> pour la recherche. Si vous ne connaissez pas, Algolia c&#8217;est un service cloud qui indexe votre contenu et vous fournit une API de recherche ultra-rapide. Sur le papier c&#8217;est g\u00e9nial et dans la pratique aussi d&#8217;ailleurs sauf que voil\u00e0, \u00e7a co\u00fbte des sous. Et mon site rencontre un franc succ\u00e8s ces derniers temps (merci \u00e0 vous !), donc j&#8217;ai de plus en plus de visiteurs, donc de plus en plus de recherches, donc une facture Algolia qui grimpe gentiment chaque mois.<\/p>\n<p>Du coup je me suis dit : \u00ab\u00a0<em>Et si je trouvais une solution de recherche pour sites statiques ?<\/em>\u00a0\u00bb Parce que oui, \u00e7a existe et c&#8217;est comme \u00e7a que j&#8217;ai d\u00e9couvert <strong>Pagefind<\/strong>.<\/p>\n<p>Pagefind c&#8217;est donc un moteur de recherche statique open source d\u00e9velopp\u00e9 par CloudCannon qui fonctionne comme ceci : Au moment du build de votre site, Pagefind parcourt tout votre HTML g\u00e9n\u00e9r\u00e9 et cr\u00e9e un index de recherche qu&#8217;on peut interroger avec un peu de JS. Y&#8217;a donc plus d&#8217;API, et tout se fait localement sur le navigateur des internautes.<\/p>\n<p>Bref, \u00e7a avait l&#8217;air tr\u00e8s cool alors \u00e9videmment, je me suis lanc\u00e9 dans l&#8217;aventure et comme j&#8217;aime bien me compliquer la vie, j&#8217;ai d\u00e9cid\u00e9 de pas juste int\u00e9grer Pagefind tel quel. Non non. J&#8217;ai voulu recr\u00e9er l&#8217;interface du Google de 1998 parce que \u00e0 quoi bon avoir son propre site web si on peut pas s&#8217;amuser un peu ^^.<\/p>\n<p>Laissez-moi donc vous raconter cette aventure.<\/p>\n<h2>Le probl\u00e8me avec Algolia<\/h2>\n<p>Leur service est excellent, je dis pas le contraire, la recherche est rapide, les r\u00e9sultats sont pertinents, l&#8217;API est bien foutue mais voil\u00e0, y&#8217;a le mod\u00e8le de pricing puisque Algolia facture au nombre de requ\u00eates de recherche.<\/p>\n<p>Plus les gens cherchent sur votre site, plus vous payez et quand vous avez un site qui fait plusieurs millions de pages vues par mois, bah&#8230; \u00e7a chiffre vite. En gros je d\u00e9passe tr\u00e8s vite les 10 000 recherches offertes chaque semaine et ensuite \u00e7a chiffre. C&#8217;est pas la mort, mais c&#8217;est un co\u00fbt r\u00e9current d\u00e9bile pour un truc qui pourrait \u00eatre gratuit.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/korben.info\/cdn-cgi\/image\/width=1200,fit=scale-down,quality=90,f=avif\/pagefind-recherche-statique-hugo-algolia-alternative\/pagefind-recherche-statique-hugo-algolia-alternative-2.png\" alt=\"\" loading=\"lazy\"><\/p>\n<p>En plus de \u00e7a, y&#8217;a la d\u00e9pendance \u00e0 un service externe. Si Algolia tombe, ma recherche tombe. Et si Algolia change ses prix, je vais devoir subir. M\u00eame chose si Algolia d\u00e9cide de modifier son API&#8230; il faudra que j&#8217;adapte mon code. Bref, c&#8217;est le cloud dans toute sa splendeur&#8230; C&#8217;est pratique mais on n&#8217;est jamais vraiment chez nous.<\/p>\n<h2>Pagefind \u00e0 la rescousse<\/h2>\n<p>Pagefind r\u00e9sout donc tous ces probl\u00e8mes d&#8217;un coup. C&#8217;est un outil en ligne de commande qui s&#8217;ex\u00e9cute apr\u00e8s votre g\u00e9n\u00e9rateur de site statique (Hugo dans mon cas, mais \u00e7a marche avec Jekyll, Eleventy, Astro, ou n&#8217;importe quoi d&#8217;autre).<\/p>\n<p>Concr\u00e8tement, vous lancez :<\/p>\n<div class=\"highlight\">\n<pre class=\"chroma\"><code class=\"language-fallback\" data-lang=\"fallback\"><span class=\"line\"><span class=\"cl\">npx pagefind --site public\n<\/span><\/span><\/code><\/pre>\n<p>Et Pagefind va :<\/p>\n<ul>\n<li>\n<ol>\n<li>Scanner tous vos fichiers HTML dans le dossier <code>public\/<\/code><\/li>\n<\/ol>\n<\/li>\n<li>\n<ol start=\"2\">\n<li>Extraire le contenu textuel (en ignorant la nav, le footer, les pubs si vous lui dites)<\/li>\n<\/ol>\n<\/li>\n<li>\n<ol start=\"3\">\n<li>Cr\u00e9er un index de recherche optimis\u00e9<\/li>\n<\/ol>\n<\/li>\n<li>\n<ol start=\"4\">\n<li>G\u00e9n\u00e9rer des fichiers JavaScript pour interroger cet index c\u00f4t\u00e9 client<\/li>\n<\/ol>\n<\/li>\n<\/ul>\n<p>Et le r\u00e9sultat c&#8217;est un dossier <code>pagefind\/<\/code> qui contient tout ce qu&#8217;il faut. Ensuite; \u00e0 vous de servir ces fichiers statiquement avec le reste de votre site, et la magie pourra op\u00e9rer !<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/korben.info\/cdn-cgi\/image\/width=1200,fit=scale-down,quality=90,f=avif\/pagefind-recherche-statique-hugo-algolia-alternative\/pagefind-recherche-statique-hugo-algolia-alternative-3.png\" alt=\"\" loading=\"lazy\"><\/p>\n<p>L&#8217;index pour mes 18 000 articles fait environ 1,5 Go. \u00c7a peut para\u00eetre beaucoup, mais Pagefind est malin car il d\u00e9coupe l&#8217;index en fragments et ne charge que ce qui est n\u00e9cessaire pour la recherche en cours. Du coup en pratique, une recherche typique t\u00e9l\u00e9charge quelques centaines de Ko, et pas plus.<\/p>\n<h2>L&#8217;int\u00e9gration technique<\/h2>\n<p>Pour int\u00e9grer<br \/>\n<a href=\"https:\/\/pagefind.app\/\">Pagefind<\/a><br \/>\ndans mon workflow Hugo, j&#8217;ai donc \u00e9t\u00e9 cherch\u00e9 le binaire, je l&#8217;ai mis sur mon serveur et je l&#8217;ai appel\u00e9 dans un cron comme \u00e7a, je rafraichi l&#8217;index de recherche 1 fois par jour (et pas \u00e0 chaque g\u00e9n\u00e9ration du site).<\/p>\n<div class=\"highlight\">\n<pre class=\"chroma\"><code class=\"language-gdscript3\" data-lang=\"gdscript3\"><span class=\"line\"><span class=\"cl\"><span class=\"mi\">0<\/span> <span class=\"mi\">4<\/span> <span class=\"o\">*<\/span> <span class=\"o\">*<\/span> <span class=\"o\">*<\/span> <span class=\"o\">\/<\/span><span class=\"n\">home<\/span><span class=\"o\">\/<\/span><span class=\"n\">manu<\/span><span class=\"o\">\/<\/span><span class=\"n\">pagefind<\/span><span class=\"o\">\/<\/span><span class=\"n\">pagefind<\/span> <span class=\"o\">--<\/span><span class=\"n\">site<\/span> <span class=\"o\">\/<\/span><span class=\"n\">home<\/span><span class=\"o\">\/<\/span><span class=\"n\">manu<\/span><span class=\"o\">\/<\/span><span class=\"n\">public_html<\/span> <span class=\"o\">--<\/span><span class=\"n\">output<\/span><span class=\"o\">-<\/span><span class=\"n\">path<\/span> <span class=\"o\">\/<\/span><span class=\"n\">home<\/span><span class=\"o\">\/<\/span><span class=\"n\">manu<\/span><span class=\"o\">\/<\/span><span class=\"n\">public_html<\/span><span class=\"o\">\/<\/span><span class=\"n\">pagefind<\/span> <span class=\"o\">&gt;&gt;<\/span> <span class=\"o\">\/<\/span><span class=\"k\">var<\/span><span class=\"o\">\/<\/span><span class=\"nb\">log<\/span><span class=\"o\">\/<\/span><span class=\"n\">pagefind<\/span><span class=\"o\">.<\/span><span class=\"n\">log<\/span> <span class=\"mi\">2<\/span><span class=\"o\">&gt;&amp;<\/span><span class=\"mi\">1<\/span>\n<\/span><\/span><\/code><\/pre>\n<p>J&#8217;ai aussi cr\u00e9\u00e9 un fichier de configuration <code>pagefind.yml<\/code> pour affiner le comportement :<\/p>\n<div class=\"highlight\">\n<pre class=\"chroma\"><code class=\"language-fallback\" data-lang=\"fallback\"><span class=\"line\"><span class=\"cl\">root_selector: \"[data-pagefind-body]\"\n<\/span><\/span><span class=\"line\"><span class=\"cl\">exclude_selectors:\n<\/span><\/span><span class=\"line\"><span class=\"cl\"> - \"header\"\n<\/span><\/span><span class=\"line\"><span class=\"cl\"> - \".site-header\"\n<\/span><\/span><span class=\"line\"><span class=\"cl\"> - \"footer\"\n<\/span><\/span><span class=\"line\"><span class=\"cl\"> - \".sidebar\"\n<\/span><\/span><\/code><\/pre>\n<p>L&#8217;astuce ici c&#8217;est d&#8217;indexer uniquement les div ayant la class <code>data-pagefind-body='true'<\/code> et d&#8217;exclure les \u00e9l\u00e9ments qui ne font pas partie du contenu \u00e9ditorial afin de ne pas indexer ce qui se trouve dans le header, les natives, le footer&#8230;etc.<\/p>\n<p>C\u00f4t\u00e9 JavaScript, Pagefind utilise les imports ES6 dynamiques. \u00c7a veut dire que le moteur de recherche n&#8217;est charg\u00e9 que quand l&#8217;utilisateur lance effectivement une recherche :<\/p>\n<div class=\"highlight\">\n<pre class=\"chroma\"><code class=\"language-fallback\" data-lang=\"fallback\"><span class=\"line\"><span class=\"cl\">async function initPagefind() {\n<\/span><\/span><span class=\"line\"><span class=\"cl\">pagefind = await import('\/pagefind\/pagefind.js');\n<\/span><\/span><span class=\"line\"><span class=\"cl\">await pagefind.init();\n<\/span><\/span><span class=\"line\"><span class=\"cl\">}\n<\/span><\/span><\/code><\/pre>\n<p>Et pour faire une recherche :<\/p>\n<div class=\"highlight\">\n<pre class=\"chroma\"><code class=\"language-gdscript3\" data-lang=\"gdscript3\"><span class=\"line\"><span class=\"cl\"><span class=\"k\">const<\/span> <span class=\"n\">search<\/span> <span class=\"o\">=<\/span> <span class=\"n\">await<\/span> <span class=\"n\">pagefind<\/span><span class=\"o\">.<\/span><span class=\"n\">search<\/span><span class=\"p\">(<\/span><span class=\"s2\">\"linux\"<\/span><span class=\"p\">);<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"o\">\/\/<\/span> <span class=\"n\">search<\/span><span class=\"o\">.<\/span><span class=\"n\">results<\/span> <span class=\"n\">contient<\/span> <span class=\"n\">les<\/span> <span class=\"n\">IDs<\/span> <span class=\"n\">des<\/span> <span class=\"n\">r\u00e9sultats<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"o\">\/\/<\/span> <span class=\"n\">On<\/span> <span class=\"n\">charge<\/span> <span class=\"n\">le<\/span> <span class=\"n\">contenu<\/span> <span class=\"n\">de<\/span> <span class=\"n\">chaque<\/span> <span class=\"n\">r\u00e9sultat<\/span> <span class=\"err\">\u00e0<\/span> <span class=\"n\">la<\/span> <span class=\"n\">demande<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"k\">for<\/span> <span class=\"p\">(<\/span><span class=\"k\">const<\/span> <span class=\"n\">result<\/span> <span class=\"n\">of<\/span> <span class=\"n\">search<\/span><span class=\"o\">.<\/span><span class=\"n\">results<\/span><span class=\"p\">)<\/span> <span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"> <span class=\"k\">const<\/span> <span class=\"n\">data<\/span> <span class=\"o\">=<\/span> <span class=\"n\">await<\/span> <span class=\"n\">result<\/span><span class=\"o\">.<\/span><span class=\"n\">data<\/span><span class=\"p\">();<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"> <span class=\"n\">console<\/span><span class=\"o\">.<\/span><span class=\"n\">log<\/span><span class=\"p\">(<\/span><span class=\"n\">data<\/span><span class=\"o\">.<\/span><span class=\"n\">url<\/span><span class=\"p\">,<\/span> <span class=\"n\">data<\/span><span class=\"o\">.<\/span><span class=\"n\">meta<\/span><span class=\"o\">.<\/span><span class=\"n\">title<\/span><span class=\"p\">,<\/span> <span class=\"n\">data<\/span><span class=\"o\">.<\/span><span class=\"n\">excerpt<\/span><span class=\"p\">);<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">}<\/span>\n<\/span><\/span><\/code><\/pre>\n<p>C&#8217;est bien fichu parce que <code>search.results<\/code> retourne imm\u00e9diatement les r\u00e9f\u00e9rences des r\u00e9sultats, mais le contenu r\u00e9el (titre, extrait, URL) n&#8217;est charg\u00e9 que quand vous appelez <code>result.data()<\/code>. Du coup vous pouvez impl\u00e9menter une pagination propre sans t\u00e9l\u00e9charger les donn\u00e9es de milliers de r\u00e9sultats d&#8217;un coup.<\/p>\n<h2>Le d\u00e9lire r\u00e9tro &#8211; Recr\u00e9er Google 1998<\/h2>\n<p>Maintenant que j&#8217;avais un moteur de recherche fonctionnel, fallait l&#8217;habiller. Et c&#8217;est l\u00e0 que j&#8217;ai eu cette id\u00e9e un peu d\u00e9bile : <strong>Pourquoi pas recr\u00e9er l&#8217;interface du Google de 1998 ?<\/strong><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/korben.info\/cdn-cgi\/image\/width=1200,fit=scale-down,quality=90,f=avif\/pagefind-recherche-statique-hugo-algolia-alternative\/pagefind-recherche-statique-hugo-algolia-alternative-1.webp\" alt=\"\" loading=\"lazy\"><\/p>\n<p>Pour les plus jeunes qui lisent \u00e7a, Google en 1998 c&#8217;\u00e9tait une page blanche avec un logo, un champ de recherche, et deux boutons : \u00ab\u00a0<em>Google Search<\/em>\u00a0\u00bb et \u00ab\u00a0<em>I&#8217;m Feeling Lucky<\/em>\u00ab\u00a0. Pas de suggestions, pas de carrousels, pas de pubs&#8230; Juste un champs de recherche. C&#8217;\u00e9tait la belle \u00e9poque !<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/korben.info\/cdn-cgi\/image\/width=1200,fit=scale-down,quality=90,f=avif\/pagefind-recherche-statique-hugo-algolia-alternative\/pagefind-recherche-statique-hugo-algolia-alternative-2.webp\" alt=\"\" loading=\"lazy\"><\/p>\n<p>J&#8217;ai donc cr\u00e9\u00e9 une page de recherche avec deux vues distinctes. La page d&#8217;accueil avec le logo centr\u00e9 et le champ de recherche au milieu, exactement comme le Google originel.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/korben.info\/cdn-cgi\/image\/width=1200,fit=scale-down,quality=90,f=avif\/pagefind-recherche-statique-hugo-algolia-alternative\/pagefind-recherche-statique-hugo-algolia-alternative-4.png\" alt=\"\" loading=\"lazy\"><\/p>\n<p>Et la page de r\u00e9sultats avec le logo en petit en haut \u00e0 gauche et les r\u00e9sultats en dessous.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/korben.info\/cdn-cgi\/image\/width=1200,fit=scale-down,quality=90,f=avif\/pagefind-recherche-statique-hugo-algolia-alternative\/pagefind-recherche-statique-hugo-algolia-alternative-5.png\" alt=\"\" loading=\"lazy\"><\/p>\n<p>Pour le code CSS, j&#8217;ai voulu \u00eatre fid\u00e8le \u00e0 l&#8217;\u00e9poque. Times New Roman comme police par d\u00e9faut, les liens en bleu soulign\u00e9 qui deviennent violet une fois visit\u00e9s. Et surtout, les boutons avec l&#8217;effet 3D des interfaces Windows 95 :<\/p>\n<div class=\"highlight\">\n<pre class=\"chroma\"><code class=\"language-fallback\" data-lang=\"fallback\"><span class=\"line\"><span class=\"cl\">.search-button:active { border-style: inset; }\n<\/span><\/span><\/code><\/pre>\n<p>Ce <code>border: outset<\/code> et <code>border-style: inset<\/code> au clic, c&#8217;est exactement ce qui donnait cet effet de bouton en relief qu&#8217;on avait partout dans les ann\u00e9es 90. Pour moi, \u00e7a fait toute la diff\u00e9rence pour l&#8217;authenticit\u00e9. M\u00eame le logo, je l&#8217;ai volontairement \u00ab\u00a0d\u00e9grad\u00e9\u00a0\u00bb pour qu&#8217;il soit de la m\u00eame qualit\u00e9 que le logo Google d&#8217;origine.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/korben.info\/cdn-cgi\/image\/width=1200,fit=scale-down,quality=90,f=avif\/pagefind-recherche-statique-hugo-algolia-alternative\/pagefind-recherche-statique-hugo-algolia-alternative-6.png\" alt=\"\" loading=\"lazy\"><\/p>\n<h2>La pagination \u00ab\u00a0Koooooorben\u00a0\u00bb<\/h2>\n<p>Vous vous souvenez de la pagination de Google avec \u00ab\u00a0Goooooogle\u00a0\u00bb en bas de page ? Le nombre de \u00ab\u00a0o\u00a0\u00bb correspondait au nombre de pages de r\u00e9sultats. J&#8217;ai fait pareil, mais avec \u00ab\u00a0Koooooorben\u00a0\u00bb.<\/p>\n<div class=\"highlight\">\n<pre class=\"chroma\"><code class=\"language-fallback\" data-lang=\"fallback\"><span class=\"line\"><span class=\"cl\">let logo = 'K'; for (let i = 0; i &lt; oCount; i++)\n<\/span><\/span><span class=\"line\"><span class=\"cl\">{\n<\/span><\/span><span class=\"line\"><span class=\"cl\">logo += o;\n<\/span><\/span><span class=\"line\"><span class=\"cl\">} logo += 'rben'; }\n<\/span><\/span><\/code><\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/korben.info\/cdn-cgi\/image\/width=1200,fit=scale-down,quality=90,f=avif\/pagefind-recherche-statique-hugo-algolia-alternative\/pagefind-recherche-statique-hugo-algolia-alternative-7.png\" alt=\"\" loading=\"lazy\"><\/p>\n<p>Plus il y a de r\u00e9sultats, plus il y a de \u00ab\u00a0o\u00a0\u00bb. C&#8217;est compl\u00e8tement inutile mais \u00e7a me fait marrer \u00e0 chaque fois que je le vois.<\/p>\n<h2>Le bouton \u00ab\u00a0J&#8217;ai de la chance\u00a0\u00bb<\/h2>\n<p>Ah, le fameux \u00ab\u00a0I&#8217;m Feeling Lucky\u00a0\u00bb de Google, j&#8217;ai voulu l&#8217;impl\u00e9menter comme \u00e0 l&#8217;\u00e9poque ! Si vous tapez une recherche et cliquez sur \u00ab\u00a0J&#8217;ai de la chance\u00a0\u00bb, vous \u00eates envoy\u00e9 sur le premier r\u00e9sultat. Classique. Mais si vous cliquez sur le bouton avec le champ vide sur la home de la recherche, vous \u00eates envoy\u00e9 sur un article al\u00e9atoire parmi les +18 000 du site.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/korben.info\/cdn-cgi\/image\/width=1200,fit=scale-down,quality=90,f=avif\/pagefind-recherche-statique-hugo-algolia-alternative\/pagefind-recherche-statique-hugo-algolia-alternative-8.png\" alt=\"\" loading=\"lazy\"><\/p>\n<p>Pour \u00e7a, j&#8217;ai utilis\u00e9 une astuce : <strong>le sitemap<\/strong>. Mon Hugo g\u00e9n\u00e8re un fichier <code>sitemap.xml<\/code> qui contient toutes les URLs du site et je peux aller piocher dedans en JS :<\/p>\n<div class=\"highlight\">\n<pre class=\"chroma\"><code class=\"language-gdscript3\" data-lang=\"gdscript3\"><span class=\"line\"><span class=\"cl\"><span class=\"k\">const<\/span> <span class=\"n\">articles<\/span> <span class=\"o\">=<\/span> <span class=\"p\">[<\/span><span class=\"o\">...<\/span><span class=\"n\">xml<\/span><span class=\"o\">.<\/span><span class=\"n\">querySelectorAll<\/span><span class=\"p\">(<\/span><span class=\"s1\">'loc'<\/span><span class=\"p\">)]<\/span> <span class=\"o\">.<\/span><span class=\"n\">map<\/span><span class=\"p\">(<\/span><span class=\"n\">loc<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"n\">loc<\/span><span class=\"o\">.<\/span><span class=\"n\">textContent<\/span><span class=\"p\">)<\/span> <span class=\"o\">.<\/span><span class=\"n\">filter<\/span><span class=\"p\">(<\/span><span class=\"n\">url<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"o\">\/\/<\/span> <span class=\"n\">Exclure<\/span> <span class=\"n\">les<\/span> <span class=\"n\">pages<\/span> <span class=\"n\">qui<\/span> <span class=\"n\">ne<\/span> <span class=\"n\">sont<\/span> <span class=\"n\">pas<\/span> <span class=\"n\">des<\/span> <span class=\"n\">articles<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"k\">const<\/span> <span class=\"n\">path<\/span> <span class=\"o\">=<\/span> <span class=\"n\">new<\/span> <span class=\"n\">URL<\/span><span class=\"p\">(<\/span><span class=\"n\">url<\/span><span class=\"p\">)<\/span><span class=\"o\">.<\/span><span class=\"n\">pathname<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"k\">return<\/span> <span class=\"o\">!<\/span><span class=\"n\">path<\/span><span class=\"o\">.<\/span><span class=\"n\">startsWith<\/span><span class=\"p\">(<\/span><span class=\"s1\">'\/categories\/'<\/span><span class=\"p\">)<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"o\">!<\/span><span class=\"n\">path<\/span><span class=\"o\">.<\/span><span class=\"n\">startsWith<\/span><span class=\"p\">(<\/span><span class=\"s1\">'\/page\/'<\/span><span class=\"p\">)<\/span> <span class=\"o\">&amp;&amp;<\/span> <span class=\"n\">path<\/span> <span class=\"o\">!==<\/span> <span class=\"s1\">'\/'<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">});<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"k\">const<\/span> <span class=\"n\">randomUrl<\/span> <span class=\"o\">=<\/span> <span class=\"n\">articles<\/span><span class=\"p\">[<\/span><span class=\"n\">Math<\/span><span class=\"o\">.<\/span><span class=\"n\">floor<\/span><span class=\"p\">(<\/span><span class=\"n\">Math<\/span><span class=\"o\">.<\/span><span class=\"n\">random<\/span><span class=\"p\">()<\/span> <span class=\"o\">*<\/span> <span class=\"n\">articles<\/span><span class=\"o\">.<\/span><span class=\"n\">length<\/span><span class=\"p\">)];<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"n\">window<\/span><span class=\"o\">.<\/span><span class=\"n\">location<\/span><span class=\"o\">.<\/span><span class=\"n\">href<\/span> <span class=\"o\">=<\/span> <span class=\"n\">randomUrl<\/span><span class=\"p\">;<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">}<\/span> <span class=\"p\">}<\/span>\n<\/span><\/span><\/code><\/pre>\n<p>Un seul fetch, un peu de parsing XML natif, et hop c&#8217;est le grand retour de la fonctionnalit\u00e9 \u00ab\u00a0article al\u00e9atoire\u00a0\u00bb qui vous manquait, je le sais !<\/p>\n<h2>Tri et nombre de r\u00e9sultats<\/h2>\n<p>Je vous ai aussi mis une listbox qui vous permet d&#8217;afficher 10, 25 ou 50 r\u00e9sultats ainsi qu&#8217;un tri par pertinence ou data. Et \u00e7a aussi Pagefind sait parfaitement le navigateur.<\/p>\n<h2>Mode sombre et accessibilit\u00e9<\/h2>\n<p>M\u00eame si l&#8217;interface est r\u00e9tro, j&#8217;ai quand m\u00eame ajout\u00e9 quelques fonctionnalit\u00e9s modernes. Le mode sombre respecte les pr\u00e9f\u00e9rences syst\u00e8me, et j&#8217;ai int\u00e9gr\u00e9 la police OpenDyslexic pour les personnes dyslexiques.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/korben.info\/cdn-cgi\/image\/width=1200,fit=scale-down,quality=90,f=avif\/pagefind-recherche-statique-hugo-algolia-alternative\/pagefind-recherche-statique-hugo-algolia-alternative-9.png\" alt=\"\" loading=\"lazy\"><\/p>\n<p>Le truc important c&#8217;est de charger ces pr\u00e9f\u00e9rences avant le rendu de la page pour \u00e9viter le fameux flash. J&#8217;ai donc un petit script qui lit les pr\u00e9f\u00e9rences dans le localStorage et applique les classes CSS imm\u00e9diatement :<\/p>\n<div class=\"highlight\">\n<pre class=\"chroma\"><code class=\"language-fallback\" data-lang=\"fallback\"><span class=\"line\"><span class=\"cl\">function() {\n<\/span><\/span><span class=\"line\"><span class=\"cl\"> if (localStorage.getItem('theme') === 'dark') {\n<\/span><\/span><span class=\"line\"><span class=\"cl\"> document.documentElement.classList.add('dark-mode');\n<\/span><\/span><span class=\"line\"><span class=\"cl\"> }\n<\/span><\/span><span class=\"line\"><span class=\"cl\"> if (localStorage.getItem('dyslexic-font') === 'true') {\n<\/span><\/span><span class=\"line\"><span class=\"cl\"> document.documentElement.classList.add('dyslexic-mode');\n<\/span><\/span><span class=\"line\"><span class=\"cl\"> }\n<\/span><\/span><span class=\"line\"><span class=\"cl\">});\n<\/span><\/span><\/code><\/pre>\n<h2>Gestion de l&#8217;historique navigateur<\/h2>\n<p>Un d\u00e9tail qui peut sembler anodin mais qui est super important pour l&#8217;exp\u00e9rience utilisateur c&#8217;est la gestion du bouton retour du navigateur.<\/p>\n<p>Quand vous faites une recherche, l&#8217;URL change selon votre requ\u00eate du genre <code>\/recherche\/?q=linux&amp;p=2<\/code>. Du coup si vous partagez cette URL \u00e0 un coll\u00e8gue, la personne arrivera directement sur les r\u00e9sultats de recherche. Et si vous utilisez le bouton retour, vous reviendrez alors \u00e0 la recherche pr\u00e9c\u00e9dente.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/korben.info\/cdn-cgi\/image\/width=1200,fit=scale-down,quality=90,f=avif\/pagefind-recherche-statique-hugo-algolia-alternative\/pagefind-recherche-statique-hugo-algolia-alternative-10.png\" alt=\"\" loading=\"lazy\"><\/p>\n<div class=\"highlight\">\n<pre class=\"chroma\"><code class=\"language-gdscript3\" data-lang=\"gdscript3\"><span class=\"line\"><span class=\"cl\"><span class=\"n\">window<\/span><span class=\"o\">.<\/span><span class=\"n\">addEventListener<\/span><span class=\"p\">(<\/span><span class=\"s1\">'popstate'<\/span><span class=\"p\">,<\/span> <span class=\"p\">()<\/span> <span class=\"o\">=&gt;<\/span> <span class=\"p\">{<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"k\">const<\/span> <span class=\"n\">query<\/span> <span class=\"o\">=<\/span> <span class=\"n\">new<\/span> <span class=\"n\">URLSearchParams<\/span><span class=\"p\">(<\/span><span class=\"n\">location<\/span><span class=\"o\">.<\/span><span class=\"n\">search<\/span><span class=\"p\">)<\/span><span class=\"o\">.<\/span><span class=\"n\">get<\/span><span class=\"p\">(<\/span><span class=\"s1\">'q'<\/span><span class=\"p\">);<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"k\">if<\/span> <span class=\"p\">(<\/span><span class=\"n\">query<\/span><span class=\"p\">)<\/span> <span class=\"n\">doSearch<\/span><span class=\"p\">(<\/span><span class=\"n\">query<\/span><span class=\"p\">);<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"k\">else<\/span> <span class=\"n\">showHomePage<\/span><span class=\"p\">();<\/span>\n<\/span><\/span><span class=\"line\"><span class=\"cl\"><span class=\"p\">});<\/span>\n<\/span><\/span><\/code><\/pre>\n<h2>Liens vers d&#8217;autres moteurs<\/h2>\n<p>Et si vous ne trouvez pas votre bonheur dans mes +18 000 articles (ce qui serait quand m\u00eame \u00e9tonnant ^^), j&#8217;ai ajout\u00e9 des liens pour relancer la m\u00eame recherche sur Google, DuckDuckGo, Qwant, Brave et Ecosia. Bref, un petit service bonus pour mes visiteurs, exactement comme le proposait Google \u00e0 l&#8217;\u00e9poque.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/korback.info\/wp-content\/uploads\/2025\/12\/SCR-20251214-nzen-1024x165.png\" alt=\"\" loading=\"lazy\"><\/p>\n<h2>Le bilan &#8211; Algolia vs Pagefind<\/h2>\n<p>Apr\u00e8s 1 semaine d&#8217;utilisation, voici donc mon verdict ! C\u00f4t\u00e9 portefeuille d&#8217;abord, Algolia me co\u00fbtait entre 60 et +100 euros par mois et maintenant pour Pagefind, c&#8217;est z\u00e9ro euros ! Et les performances sont \u00e9galement au rendez-vous. Algolia c&#8217;\u00e9tait rapide et bien l\u00e0, \u00e7a l&#8217;est encore plus. Seul compromis \u00e0 noter, l&#8217;index Algolia se mettait \u00e0 jour en temps r\u00e9el, alors que Pagefind n\u00e9cessite une reconstruction au moment du build.<\/p>\n<h2>La conclusion<\/h2>\n<p>Voil\u00e0, j&#8217;ai maintenant une recherche qui marche vraiment bien, qui me co\u00fbte 0\u20ac par mois, et qui a un look r\u00e9tro qui va en surprendre plus d&#8217;un&#8230;<\/p>\n<p>Alors est-ce que c&#8217;\u00e9tait n\u00e9cessaire de passer autant de temps sur le design r\u00e9tro ? H\u00e9 bien absolument pas. Mais est-ce que \u00e7a valait le coup ?<\/p>\n<p>Franchement, oui !! C&#8217;est mon site, je fais ce que je veux, et si \u00e7a peut faire sourire quelques visiteurs nostalgiques des d\u00e9buts du web, c&#8217;est du bonus.<br \/>\n<a href=\"https:\/\/patreon.com\/korben\">D&#8217;ailleurs un grand merci aux Patreons<\/a><br \/>\nqui me soutiennent car sans eux, je n&#8217;aurais pas pu passer mon dimanche l\u00e0 dessus ^^<\/p>\n<p>Et puis surtout, \u00e7a m&#8217;a permis de d\u00e9couvrir Pagefind qui est vraiment un excellent outil. Donc si vous avez un site statique (ou n&#8217;importe quel type de contenu textuel) et que vous cherchez une solution de recherche gratuite et performante, je vous le recommande chaudement. La documentation est claire, l&#8217;int\u00e9gration est simple, et le r\u00e9sultat est top !<\/p>\n<p>Allez, maintenant<br \/>\n<a href=\"https:\/\/korben.info\/recherche\/\">vous pouvez aller tester la nouvelle recherche sur le site<\/a><br \/>\n. Et si vous cliquez sur \u00ab\u00a0J&#8217;ai de la chance\u00a0\u00bb sans rien taper&#8230; bonne d\u00e9couverte !<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Bon, faut qu&#8217;on parle un peu du moteur de recherche de mon site. Ceux qui l&#8217;ont d\u00e9j\u00e0 utilis\u00e9 savent de quoi je parle : c&#8217;\u00e9tait pas terrible. Enfin, \u00ab\u00a0pas terrible\u00a0\u00bb j&#8217;suis gentil. C&#8217;est un esp\u00e8ce d&#8217;overlay avec des r\u00e9sultats certes fiables mais c&#8217;\u00e9tait vraiment pas pratique. Et en plus de \u00e7a, comme j&#8217;ai un site statique g\u00e9n\u00e9r\u00e9 avec Hugo, je passais par Algolia pour la recherche. Si vous ne connaissez pas, Algolia c&#8217;est un service cloud qui indexe votre contenu et vous fournit une API de recherche ultra-rapide. Sur le papier c&#8217;est g\u00e9nial et dans la pratique aussi d&#8217;ailleurs sauf que voil\u00e0, \u00e7a co\u00fbte des sous. Et mon site rencontre un franc succ\u00e8s ces derniers temps (merci \u00e0 vous !), donc j&#8217;ai de plus en plus de visiteurs, donc de plus en plus de recherches, donc une facture Algolia qui grimpe gentiment chaque mois. Du coup je me suis dit : \u00ab\u00a0Et si je trouvais une solution de recherche pour sites statiques ?\u00a0\u00bb Parce que oui, \u00e7a existe et c&#8217;est comme \u00e7a que j&#8217;ai d\u00e9couvert Pagefind. Pagefind c&#8217;est donc un moteur de recherche statique open source d\u00e9velopp\u00e9 par CloudCannon qui fonctionne comme ceci : Au moment du build de votre site, Pagefind parcourt tout votre HTML g\u00e9n\u00e9r\u00e9 et cr\u00e9e un index de recherche qu&#8217;on peut interroger avec un peu de JS. Y&#8217;a donc plus d&#8217;API, et tout se fait localement sur le navigateur des internautes. Bref, \u00e7a avait l&#8217;air tr\u00e8s cool alors \u00e9videmment, je me suis lanc\u00e9 dans l&#8217;aventure et comme j&#8217;aime bien me compliquer la vie, j&#8217;ai d\u00e9cid\u00e9 de pas juste int\u00e9grer Pagefind tel quel. Non non. J&#8217;ai voulu recr\u00e9er l&#8217;interface du Google de 1998 parce que \u00e0 quoi bon avoir son propre site web si on peut pas s&#8217;amuser un peu ^^. Laissez-moi donc vous raconter cette aventure. Le probl\u00e8me avec Algolia Leur service est excellent, je dis pas le contraire, la recherche est rapide, les r\u00e9sultats sont pertinents, l&#8217;API est bien foutue mais voil\u00e0, y&#8217;a le mod\u00e8le de pricing puisque Algolia facture au nombre de requ\u00eates de recherche. Plus les gens cherchent sur votre site, plus vous payez et quand vous avez un site qui fait plusieurs millions de pages vues par mois, bah&#8230; \u00e7a chiffre vite. En gros je d\u00e9passe tr\u00e8s vite les 10 000 recherches offertes chaque semaine et ensuite \u00e7a chiffre. C&#8217;est pas la mort, mais c&#8217;est un co\u00fbt r\u00e9current d\u00e9bile pour un truc qui pourrait \u00eatre gratuit. En plus de \u00e7a, y&#8217;a la d\u00e9pendance \u00e0 un service externe. Si Algolia tombe, ma recherche tombe. Et si Algolia change ses prix, je vais devoir subir. M\u00eame chose si Algolia d\u00e9cide de modifier son API&#8230; il faudra que j&#8217;adapte mon code. Bref, c&#8217;est le cloud dans toute sa splendeur&#8230; C&#8217;est pratique mais on n&#8217;est jamais vraiment chez nous. Pagefind \u00e0 la rescousse Pagefind r\u00e9sout donc tous ces probl\u00e8mes d&#8217;un coup. C&#8217;est un outil en ligne de commande qui s&#8217;ex\u00e9cute apr\u00e8s votre g\u00e9n\u00e9rateur de site statique (Hugo dans mon cas, mais \u00e7a marche avec Jekyll, Eleventy, Astro, ou n&#8217;importe quoi d&#8217;autre). Concr\u00e8tement, vous lancez : npx pagefind &#8211;site public Et Pagefind va : Scanner tous vos fichiers HTML dans le dossier public\/ Extraire le contenu textuel (en ignorant la nav, le footer, les pubs si vous lui dites) Cr\u00e9er un index de recherche optimis\u00e9 G\u00e9n\u00e9rer des fichiers JavaScript pour interroger cet index c\u00f4t\u00e9 client Et le r\u00e9sultat c&#8217;est un dossier pagefind\/ qui contient tout ce qu&#8217;il faut. Ensuite; \u00e0 vous de servir ces fichiers statiquement avec le reste de votre site, et la magie pourra op\u00e9rer ! L&#8217;index pour mes 18 000 articles fait environ 1,5 Go. \u00c7a peut para\u00eetre beaucoup, mais Pagefind est malin car il d\u00e9coupe l&#8217;index en fragments et ne charge que ce qui est n\u00e9cessaire pour la recherche en cours. Du coup en pratique, une recherche typique t\u00e9l\u00e9charge quelques centaines de Ko, et pas plus. L&#8217;int\u00e9gration technique Pour int\u00e9grer Pagefind dans mon workflow Hugo, j&#8217;ai donc \u00e9t\u00e9 cherch\u00e9 le binaire, je l&#8217;ai mis sur mon serveur et je l&#8217;ai appel\u00e9 dans un cron comme \u00e7a, je rafraichi l&#8217;index de recherche 1 fois par jour (et pas \u00e0 chaque g\u00e9n\u00e9ration du site). 0 4 * * * \/home\/manu\/pagefind\/pagefind &#8211;site \/home\/manu\/public_html &#8211;output-path \/home\/manu\/public_html\/pagefind &gt;&gt; \/var\/log\/pagefind.log 2&gt;&amp;1 J&#8217;ai aussi cr\u00e9\u00e9 un fichier de configuration pagefind.yml pour affiner le comportement : root_selector: &#8220;[data-pagefind-body]&#8221; exclude_selectors: &#8211; &#8220;header&#8221; &#8211; &#8220;.site-header&#8221; &#8211; &#8220;footer&#8221; &#8211; &#8220;.sidebar&#8221; L&#8217;astuce ici c&#8217;est d&#8217;indexer uniquement les div ayant la class data-pagefind-body=&#8217;true&#8217; et d&#8217;exclure les \u00e9l\u00e9ments qui ne font pas partie du contenu \u00e9ditorial afin de ne pas indexer ce qui se trouve dans le header, les natives, le footer&#8230;etc. C\u00f4t\u00e9 JavaScript, Pagefind utilise les imports ES6 dynamiques. \u00c7a veut dire que le moteur de recherche n&#8217;est charg\u00e9 que quand l&#8217;utilisateur lance effectivement une recherche : async function initPagefind() { pagefind = await import(&#8216;\/pagefind\/pagefind.js&#8217;); await pagefind.init(); } Et pour faire une recherche : const search = await pagefind.search(&#8220;linux&#8221;); \/\/ search.results contient les IDs des r\u00e9sultats \/\/ On charge le contenu de chaque r\u00e9sultat \u00e0 la demande for (const result of search.results) { const data = await result.data(); console.log(data.url, data.meta.title, data.excerpt); } C&#8217;est bien fichu parce que search.results retourne imm\u00e9diatement les r\u00e9f\u00e9rences des r\u00e9sultats, mais le contenu r\u00e9el (titre, extrait, URL) n&#8217;est charg\u00e9 que quand vous appelez result.data(). Du coup vous pouvez impl\u00e9menter une pagination propre sans t\u00e9l\u00e9charger les donn\u00e9es de milliers de r\u00e9sultats d&#8217;un coup. Le d\u00e9lire r\u00e9tro &#8211; Recr\u00e9er Google 1998 Maintenant que j&#8217;avais un moteur de recherche fonctionnel, fallait l&#8217;habiller. Et c&#8217;est l\u00e0 que j&#8217;ai eu cette id\u00e9e un peu d\u00e9bile : Pourquoi pas recr\u00e9er l&#8217;interface du Google de 1998 ? Pour les plus jeunes qui lisent \u00e7a, Google en 1998 c&#8217;\u00e9tait une page blanche avec un logo, un champ de recherche, et deux boutons : \u00ab\u00a0Google Search\u00a0\u00bb et \u00ab\u00a0I&#8217;m Feeling Lucky\u00ab\u00a0. Pas de suggestions, pas de carrousels, pas de pubs&#8230; Juste un champs de recherche. C&#8217;\u00e9tait la belle \u00e9poque ! J&#8217;ai donc cr\u00e9\u00e9 une page de recherche avec deux vues distinctes. La page d&#8217;accueil avec le logo centr\u00e9 et le champ<\/p>\n","protected":false},"author":1,"featured_media":1948,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"give_campaign_id":0,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_kadence_starter_templates_imported_post":false,"footnotes":""},"class_list":["post-1947","page","type-page","status-publish","has-post-thumbnail","hentry"],"campaignId":"","_links":{"self":[{"href":"https:\/\/elearningsamba.com\/index.php\/wp-json\/wp\/v2\/pages\/1947","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/elearningsamba.com\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/elearningsamba.com\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/elearningsamba.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/elearningsamba.com\/index.php\/wp-json\/wp\/v2\/comments?post=1947"}],"version-history":[{"count":0,"href":"https:\/\/elearningsamba.com\/index.php\/wp-json\/wp\/v2\/pages\/1947\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/elearningsamba.com\/index.php\/wp-json\/wp\/v2\/media\/1948"}],"wp:attachment":[{"href":"https:\/\/elearningsamba.com\/index.php\/wp-json\/wp\/v2\/media?parent=1947"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}