[Nom de l'Intégration API Pagefind]
Type de ressource : API JavaScript Pagefind (Recherche programmatique)
Fichier source :src/components/ui/SearchBar.astroou composant associé
Statut : [Actif / Déprécié / En développement]
1. Vue d’ensemble
Section titled “1. Vue d’ensemble”1.1. Description
Section titled “1.1. Description”[Description concise du rôle de cette intégration API dans l’architecture de recherche du site]
1.2. Objectif
Section titled “1.2. Objectif”- [Objectif principal : ex. fournir une recherche custom avec contrôle total sur l’UI et le comportement]
- [Bénéfice par rapport à Pagefind UI natif : ex. personnalisation complète, intégration design system]
1.3. Périmètre concerné
Section titled “1.3. Périmètre concerné”- Couverture : [Vitrine / Blog / Portfolio / Documentation / Tout]
- Type d’API : [Pagefind JS API / Pagefind UI custom / Les deux]
- Emplacement : [Header global / Page dédiée / Modal / Sidebar]
- Cohérence Starlight : [Index partagé / Index séparé mais cohérent]
2. Architecture et flux
Section titled “2. Architecture et flux”2.1. Diagramme de flux
Section titled “2.1. Diagramme de flux”[Utilisateur] │ (saisie dans la barre de recherche) ▼[Composant SearchBar (Astro)] │ • Import dynamique de Pagefind │ • Initialisation de l'index │ • Debounce sur l'input ▼[Pagefind JS API] │ • Chargement de l'index chunké │ • Recherche côté client │ • Application des filtres ▼[Résultats] │ • Extraction des métadonnées │ • Distinction visuelle par type │ • Pagination / Limitation ▼[Interface custom] │ • Rendu des résultats │ • Badges (Doc, Blog, Portfolio, Page) ▼[Utilisateur] │ (clic sur un résultat) ▼[Navigation vers la page]2.2. Composants impliqués
Section titled “2.2. Composants impliqués”| Composant | Rôle | Fichier |
|---|---|---|
SearchBar.astro |
Interface de recherche globale | src/components/ui/SearchBar.astro |
SearchResults.astro |
Affichage des résultats | src/components/ui/SearchResults.astro |
SearchFilters.astro |
Filtres par type de contenu | src/components/ui/SearchFilters.astro |
| Pagefind JS API | Bibliothèque JS de recherche | pagefind (npm) |
| Index Pagefind | Index chunké généré au build | dist/_pagefind/ |
3. Implémentation de l’API Pagefind
Section titled “3. Implémentation de l’API Pagefind”3.1. Import et initialisation
Section titled “3.1. Import et initialisation”<div class="search-container" data-pagefind-ignore="all"> <input type="search" id="search-input" placeholder="Rechercher..." class="search-input" autocomplete="off" aria-label="Rechercher dans le site" />
<div id="search-results" role="region" aria-label="Résultats de recherche" aria-live="polite"> <!-- Résultats injectés dynamiquement --> </div></div>
<script> // Import dynamique de Pagefind (lazy loading) let pagefind: any = null;
async function initPagefind() { if (pagefind) return;
// Import dynamique pour éviter de charger Pagefind au chargement initial // @ts-ignore pagefind = await import('/pagefind/pagefind.js'); await pagefind.init(); }
// État de la recherche let currentQuery = ''; let activeFilters: Record<string, string[]> = {}; let searchTimeout: number;
// Recherche avec debounce async function performSearch(query: string) { if (!query || query.length < 2) { clearResults(); return; }
await initPagefind();
const options: any = {};
// Application des filtres actifs if (Object.keys(activeFilters).length > 0) { options.filters = activeFilters; }
// Exécution de la recherche const search = await pagefind.search(query, options);
// Récupération des résultats (limités à 10) const results = await Promise.all( search.results.slice(0, 10).map(result => result.data()) );
renderResults(results, search.filters); }
// Effacement des résultats function clearResults() { const container = document.getElementById('search-results'); if (container) { container.innerHTML = ''; } }
// Rendu des résultats function renderResults(results: any[], availableFilters: any) { const container = document.getElementById('search-results'); if (!container) return;
if (results.length === 0) { container.innerHTML = '<p class="no-results">Aucun résultat trouvé</p>'; return; }
const html = results.map(result => { const type = result.meta?.type || 'page'; const badge = { docs: 'Doc', blog: 'Blog', portfolio: 'Portfolio', page: 'Page', }[type] || 'Page';
return ` <article class="search-result"> <span class="badge badge-${type}">${badge}</span> <h3 class="result-title"> <a href="${result.url}">${result.meta?.title || result.url}</a> </h3> <p class="result-excerpt">${result.excerpt || ''}</p> </article> `; }).join('');
container.innerHTML = html; }
// Event listeners const searchInput = document.getElementById('search-input') as HTMLInputElement;
searchInput?.addEventListener('input', (e) => { const query = (e.target as HTMLInputElement).value; currentQuery = query;
// Debounce de 300ms clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { performSearch(query); }, 300); });
// Écoute des changements de filtres (via CustomEvent) window.addEventListener('search-filters-changed', (e: any) => { activeFilters = e.detail.filters; performSearch(currentQuery); });</script>
<style> .search-container { max-width: 600px; margin: 0 auto; position: relative; }
.search-input { width: 100%; padding: 0.75rem 1rem; border: 1px solid var(--color-border, #e5e7eb); border-radius: 0.5rem; font-size: 1rem; }
.search-result { padding: 1rem; border-bottom: 1px solid var(--color-border, #e5e7eb); }
.badge { display: inline-block; padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-size: 0.75rem; font-weight: 600; margin-bottom: 0.5rem; }
.badge-docs { background-color: #dbeafe; color: #1e40af; }
.badge-blog { background-color: #d1fae5; color: #065f46; }
.badge-portfolio { background-color: #fed7aa; color: #9a3412; }
.badge-page { background-color: #e5e7eb; color: #374151; }
.result-title { font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; }
.result-title a { color: var(--color-primary, #3b82f6); text-decoration: none; }
.result-title a:hover { text-decoration: underline; }
.result-excerpt { color: var(--color-gray-6, #4b5563); font-size: 0.875rem; line-height: 1.5; }
.no-results { text-align: center; padding: 2rem; color: var(--color-gray-5, #6b7280); }</style>3.2. Utilisation des filtres programmatiques
Section titled “3.2. Utilisation des filtres programmatiques”// Exemple de filtrage par type de contenuconst filters = { type: ['blog', 'docs'], // Uniquement les articles de blog et la documentation};
const search = await pagefind.search('installation', { filters });
// Exemple de filtrage multipleconst filters = { type: ['blog'], category: ['tech', 'design'], author: ['john-doe'],};
const search = await pagefind.search('tutorial', { filters });3.3. Extraction des métadonnées
Section titled “3.3. Extraction des métadonnées”// Structure d'un résultat Pagefindinterface PagefindResult { id: string; url: string; score: number; data: { url: string; meta: { title?: string; description?: string; type?: string; // 'docs' | 'blog' | 'portfolio' | 'page' category?: string; author?: string; image?: string; }; content: string; excerpt: string; word_count: number; filters: Record<string, string[]>; sub_results: Array<{ title: string; url: string; excerpt: string; }>; };}
// Extraction des métadonnéesconst result = await search.results[0].data();const title = result.meta?.title || 'Sans titre';const type = result.meta?.type || 'page';const excerpt = result.excerpt || result.content.substring(0, 200);3.4. Gestion des sous-résultats
Section titled “3.4. Gestion des sous-résultats”// Pagefind peut retourner des sous-résultats (sections de page)const result = await search.results[0].data();
if (result.sub_results && result.sub_results.length > 0) { // Afficher les sous-résultats result.sub_results.forEach(sub => { console.log(`Section: ${sub.title}`); console.log(`URL: ${sub.url}`); console.log(`Extrait: ${sub.excerpt}`); });}4. Composant de filtres custom
Section titled “4. Composant de filtres custom”<div class="search-filters" role="group" aria-label="Filtres de recherche"> <fieldset> <legend class="sr-only">Filtrer par type de contenu</legend>
<label class="filter-label"> <input type="checkbox" value="docs" checked /> <span class="filter-badge badge-docs">Doc</span> Documentation </label>
<label class="filter-label"> <input type="checkbox" value="blog" checked /> <span class="filter-badge badge-blog">Blog</span> Blog </label>
<label class="filter-label"> <input type="checkbox" value="portfolio" checked /> <span class="filter-badge badge-portfolio">Portfolio</span> Portfolio </label>
<label class="filter-label"> <input type="checkbox" value="page" checked /> <span class="filter-badge badge-page">Page</span> Pages </label> </fieldset></div>
<script> const filterCheckboxes = document.querySelectorAll<HTMLInputElement>('.search-filters input[type="checkbox"]');
filterCheckboxes.forEach((checkbox) => { checkbox.addEventListener('change', () => { const activeFilters: Record<string, string[]> = {};
const checkedTypes = Array.from(filterCheckboxes) .filter((cb) => cb.checked) .map((cb) => cb.value);
if (checkedTypes.length > 0 && checkedTypes.length < 4) { activeFilters.type = checkedTypes; }
// Déclencher une nouvelle recherche avec les filtres actifs window.dispatchEvent(new CustomEvent('search-filters-changed', { detail: { filters: activeFilters }, })); }); });</script>
<style> .search-filters { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 1rem; }
.filter-label { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0.75rem; border: 1px solid var(--color-border, #e5e7eb); border-radius: 0.5rem; cursor: pointer; transition: all 0.2s; }
.filter-label:hover { background-color: var(--color-gray-1, #f3f4f6); }
.filter-label input[type="checkbox"] { width: 1rem; height: 1rem; }
.filter-badge { font-size: 0.75rem; padding: 0.125rem 0.375rem; }</style>5. Métadonnées Pagefind dans le frontmatter
Section titled “5. Métadonnées Pagefind dans le frontmatter”5.1. Configuration par type de contenu
Section titled “5.1. Configuration par type de contenu”---# Article de blogtitle: "Mon article de blog"description: "Description de l'article"pagefind: meta: type: "blog" category: "tech" author: "john-doe" filters: - type: blog - category: tech - author: john-doe------# Page de documentation Starlighttitle: "Guide d'installation"description: "Comment installer le projet"sidebar: label: "Installation" order: 1pagefind: meta: type: "docs" category: "guide" filters: - type: docs - category: guide------# Projet portfoliotitle: "Projet Alpha"description: "Description du projet"pagefind: meta: type: "portfolio" category: "web" filters: - type: portfolio - category: web---5.2. Métadonnées via attributs HTML
Section titled “5.2. Métadonnées via attributs HTML”<!-- Définir des métadonnées Pagefind directement dans le HTML --><article data-pagefind-meta="type:blog,category:tech,author:john-doe"> <h1 data-pagefind-weight="2.0">Titre de l'article</h1> <div data-pagefind-body> <!-- Contenu principal --> </div></article>6. Intégration avec Starlight
Section titled “6. Intégration avec Starlight”6.1. Cohérence de l’index
Section titled “6.1. Cohérence de l’index”- ✅ Starlight utilise nativement Pagefind pour sa propre barre de recherche
- ✅ L’objectif est de partager le même index (ou des index très proches)
- ✅ Les résultats de documentation sont distingués par le badge “Doc”
- ✅ L’expérience de recherche est homogène sur tout le site
6.2. Personnalisation du thème Starlight (optionnel)
Section titled “6.2. Personnalisation du thème Starlight (optionnel)”/* Aligner les styles de recherche Starlight avec le reste du site */:root { --sl-color-accent: var(--color-primary, #3b82f6);}
starlight-search { --sl-search-border: var(--color-border, #e5e7eb); --sl-search-background: var(--color-bg-primary, #ffffff);}7. Accessibilité (a11y)
Section titled “7. Accessibilité (a11y)”7.1. Conformité WCAG 2.1 AA
Section titled “7.1. Conformité WCAG 2.1 AA”| Critère | Implémentation | Statut |
|---|---|---|
| 1.3.1 Info and Relationships | Label explicite pour le champ de recherche | ✅ |
| 2.1.1 Keyboard | Navigation clavier complète (Tab, Enter, Escape) | ✅ |
| 2.4.3 Focus Order | Ordre de focus logique (input → filtres → résultats) | ✅ |
| 2.4.7 Focus Visible | Focus visible sur tous les éléments interactifs | ✅ |
| 3.3.2 Labels or Instructions | Placeholder descriptif + label visible | ✅ |
| 4.1.2 Name, Role, Value | Attributs ARIA appropriés (aria-label, aria-live) |
✅ |
7.2. Attributs ARIA utilisés
Section titled “7.2. Attributs ARIA utilisés”<!-- Champ de recherche --><input type="search" id="search-input" aria-label="Rechercher dans le site" placeholder="Rechercher..." autocomplete="off"/>
<!-- Conteneur de résultats (annoncé aux lecteurs d'écran) --><div id="search-results" role="region" aria-label="Résultats de recherche" aria-live="polite"> <!-- Résultats --></div>
<!-- Filtres --><fieldset aria-label="Filtres de recherche"> <legend class="sr-only">Filtrer par type de contenu</legend> <!-- Checkboxes --></fieldset>8. Performance et optimisation
Section titled “8. Performance et optimisation”8.1. Chargement de l’index
Section titled “8.1. Chargement de l’index”- ✅ L’index Pagefind est chunké (chargé à la demande)
- ✅ Seul le script principal (
pagefind.js) est chargé initialement (~10 KB) - ✅ Les fragments d’index sont chargés au fur et à mesure des recherches
- ✅ Import dynamique pour éviter de charger Pagefind au chargement initial
8.2. Optimisations appliquées
Section titled “8.2. Optimisations appliquées”- ✅ Debounce sur l’input (300ms) pour éviter les recherches excessives
- ✅ Limite de 10 résultats affichés (pagination si nécessaire)
- ✅ Pas d’images dans les résultats (réduit la bande passante)
- ✅ Index chunké (faible bande passante)
- ✅ Recherche côté client (pas d’appel API)
8.3. Core Web Vitals
Section titled “8.3. Core Web Vitals”| Métrique | Objectif | Stratégie |
|---|---|---|
| LCP | < 2.5s | Pas d’impact (recherche post-chargement) |
| INP | < 200ms | Debounce + recherche rapide |
| CLS | < 0.1 | Dimensions fixes pour le conteneur de résultats |
8.4. Taille des assets
Section titled “8.4. Taille des assets”| Asset | Taille | Rationale |
|---|---|---|
pagefind.js |
~10 KB | Script principal |
| Fragments d’index | ~1-5 MB total | Chunké, chargé à la demande |
9. Débogage et troubleshooting
Section titled “9. Débogage et troubleshooting”9.1. Problèmes courants
Section titled “9.1. Problèmes courants”| Symptôme | Cause probable | Solution |
|---|---|---|
| Aucun résultat | Index non généré | Vérifier que npm run build a été exécuté |
| Résultats non pertinents | Ranking mal configuré | Ajuster les poids dans pagefind.yml |
| Filtres non fonctionnels | Métadonnées manquantes | Ajouter pagefind.meta.type dans le frontmatter |
| Recherche lente | Index trop volumineux | Exclure les éléments non pertinents |
| Erreur “pagefind is not defined” | Import dynamique échoué | Vérifier le chemin /pagefind/pagefind.js |
| Styles cassés | Conflit CSS | Vérifier les spécificités et le scope |
9.2. Tests manuels
Section titled “9.2. Tests manuels”# Génération de l'indexnpm run build
# Vérification de l'indexls -lh dist/_pagefind/
# Test de la recherche en localnpm run preview# Ouvrir http://localhost:4321 et tester la recherche
# Inspection de l'index (debug)npx pagefind --source dist --verbose
# Test de l'API dans la console du navigateur# Ouvrir les DevTools → Consoleconst pagefind = await import('/pagefind/pagefind.js');await pagefind.init();const search = await pagefind.search('test');console.log(search.results);9.3. Logs et monitoring
Section titled “9.3. Logs et monitoring”# Logs de développementnpm run dev
# Vérification de la taille de l'indexdu -sh dist/_pagefind/
# Test de l'accessibiliténpx axe http://localhost:4321/10. Bonnes pratiques
Section titled “10. Bonnes pratiques”10.1. Architecture
Section titled “10.1. Architecture”- ✅ Utiliser l’import dynamique pour charger Pagefind à la demande
- ✅ Centraliser la logique de recherche dans un composant dédié
- ✅ Séparer les composants (SearchBar, SearchResults, SearchFilters)
- ✅ Utiliser des CustomEvents pour la communication entre composants
- ✅ Typer les résultats Pagefind avec TypeScript
10.2. UX
Section titled “10.2. UX”- ✅ Fournir un feedback immédiat (spinner pendant la recherche)
- ✅ Afficher un message “Aucun résultat” si la recherche est vide
- ✅ Permettre la navigation clavier complète
- ✅ Annoncer les résultats aux lecteurs d’écran (
aria-live) - ✅ Utiliser le debounce pour éviter les recherches excessives
10.3. Accessibilité
Section titled “10.3. Accessibilité”- ✅ Toujours fournir un label explicite pour le champ de recherche
- ✅ Utiliser des attributs ARIA appropriés
- ✅ Assurer une navigation clavier complète
- ✅ Vérifier le contraste (WCAG AA minimum)
- ✅ Tester avec un lecteur d’écran
10.4. Performance
Section titled “10.4. Performance”- ✅ Utiliser le debounce pour éviter les recherches excessives
- ✅ Limiter le nombre de résultats affichés
- ✅ Charger l’index de manière chunkée
- ✅ Éviter les images dans les résultats
- ✅ Mesurer les Core Web Vitals avec la recherche active
10.5. Maintenance
Section titled “10.5. Maintenance”- ✅ Documenter les changements de configuration dans ce fichier
- ✅ Tester la recherche après chaque modification majeure
- ✅ Vérifier les filtres et les métadonnées régulièrement
- ✅ Surveiller la taille de l’index (objectif < 5 MB)
11. Évolutions et roadmap
Section titled “11. Évolutions et roadmap”11.1. Améliorations planifiées
Section titled “11.1. Améliorations planifiées”- [Évolution 1 : ex. Suggestions de recherche (autocomplete)]
- [Évolution 2 : ex. Historique des recherches (localStorage)]
- [Évolution 3 : ex. Recherche facette avancée (par auteur, catégorie, date)]
- [Évolution 4 : ex. Support du multi-langue (i18n)]
- [Évolution 5 : ex. Pagination des résultats]
11.2. Limitations connues
Section titled “11.2. Limitations connues”- ❌ Pagefind ne supporte pas nativement la recherche dynamique (SSR)
- ❌ L’index doit être régénéré à chaque build (pas de mise à jour incrémentale)
- ❌ Les filtres sont limités aux métadonnées définies au build
- ❌ La recherche full-text ne supporte pas nativement les langues non-latines
12. Ressources liées
Section titled “12. Ressources liées”- Documentation Pagefind : Search API
- Documentation Pagefind : Metadata
- Documentation Pagefind : Filters
- DAT L2 : §8 - Stratégie de Recherche Unifiée (Pagefind)
- DAT L2 : §8.4 - Expérience utilisateur & distinction visuelle des résultats
- DAT L2 : §8.5 - Cohérence avec Starlight
- Fichiers concernés :
src/components/ui/SearchBar.astrosrc/components/ui/SearchResults.astrosrc/components/ui/SearchFilters.astrodist/_pagefind/(généré au build)
13. Historique des modifications
Section titled “13. Historique des modifications”| Date | Version | Description | Auteur |
|---|---|---|---|
| YYYY-MM-DD | 1.0 | Création initiale | [Nom] |
| YYYY-MM-DD | 1.1 | [Modification] | [Nom] |
---
**Points clés de ce template :**- Documente l'API JavaScript programmatique de Pagefind (par opposition à l'UI native)- Détaille l'import dynamique et l'initialisation de l'index- Inclut la gestion des filtres et des métadonnées- Fournit l'intégration avec les composants custom (SearchBar, SearchResults, SearchFilters)- Couvre l'accessibilité WCAG 2.1 AA (navigation clavier, ARIA)- Sépare clairement l'implémentation technique et les bonnes pratiques UX- Inclut un guide de débogage complet avec tests manuels dans la console