📍 Article principal du cluster : Meilisearch 2026 : le guide complet. Lisez le pilier pour la vue d’ensemble.
Vous avez un Meilisearch en production (voir tutoriel précédent) et une application Next.js 15 avec App Router. Ce tutoriel branche les deux pour livrer une expérience de recherche instantanée digne d’Algolia : autocomplete dès le 2e caractère, facettes filtrables, surlignage des termes, pagination infinie. La méthode marche pour un blog, une marketplace, un annuaire ou une documentation technique.
Prérequis
- Application Next.js 15 avec App Router opérationnelle.
- Meilisearch v1.10 accessible en HTTPS avec une API key search-only.
- Au moins un index avec quelques centaines de documents pour tester.
- Niveau : intermédiaire (React, hooks, TypeScript).
- Temps estimé : 1 à 2 heures.
Étape 1 — Installer les dépendances
npm install meilisearch instantsearch.js react-instantsearch react-instantsearch-nextjs
npm install @meilisearch/instant-meilisearch
Le package @meilisearch/instant-meilisearch fait le pont entre l’API Meilisearch et le protocole InstantSearch (compatible Algolia), permettant de réutiliser tous les composants react-instantsearch sans réécriture.
Étape 2 — Variables d’environnement
Dans .env.local :
NEXT_PUBLIC_MEILI_HOST=https://search.votre-entreprise.com
NEXT_PUBLIC_MEILI_SEARCH_KEY=votre-api-key-search-only
Important : seule la clé search-only doit être préfixée NEXT_PUBLIC_. Le master key reste côté serveur uniquement, dans MEILI_MASTER_KEY sans préfixe public.
Étape 3 — Créer le client InstantSearch
Fichier lib/search-client.ts :
'use client';
import { instantMeiliSearch } from '@meilisearch/instant-meilisearch';
export const { searchClient } = instantMeiliSearch(
process.env.NEXT_PUBLIC_MEILI_HOST!,
process.env.NEXT_PUBLIC_MEILI_SEARCH_KEY!,
{
finitePagination: true,
primaryKey: 'id',
}
);
Étape 4 — Page de recherche complète
Fichier app/recherche/page.tsx :
'use client';
import { InstantSearchNext } from 'react-instantsearch-nextjs';
import {
SearchBox, Hits, RefinementList, Pagination, Stats, Highlight,
} from 'react-instantsearch';
import { searchClient } from '@/lib/search-client';
function Hit({ hit }: { hit: any }) {
return (
<article className="border rounded p-4 hover:shadow">
<h3 className="font-bold">
<Highlight attribute="title" hit={hit} />
</h3>
<p className="text-sm text-gray-600">
<Highlight attribute="description" hit={hit} />
</p>
<p className="font-mono">{hit.price.toLocaleString()} FCFA</p>
</article>
);
}
export default function SearchPage() {
return (
<InstantSearchNext indexName="products" searchClient={searchClient} routing>
<div className="grid grid-cols-4 gap-6">
<aside className="col-span-1">
<h4>Catégorie</h4>
<RefinementList attribute="category" />
<h4>Marque</h4>
<RefinementList attribute="brand" />
</aside>
<main className="col-span-3">
<SearchBox placeholder="Rechercher..." />
<Stats />
<Hits hitComponent={Hit} />
<Pagination />
</main>
</div>
</InstantSearchNext>
);
}
Étape 5 — Configurer les attributes côté Meilisearch
Avant que les facettes fonctionnent, déclarez côté Meilisearch quels attributs sont filterables :
curl -X PATCH 'https://search.votre-entreprise.com/indexes/products/settings' \
-H 'Authorization: Bearer MASTER_KEY' \
-H 'Content-Type: application/json' \
--data '{
"filterableAttributes": ["category", "brand", "price"],
"sortableAttributes": ["price", "created_at"],
"searchableAttributes": ["title", "description", "brand"],
"displayedAttributes": ["*"]
}'
Étape 6 — Autocomplete dans le header
Pour une expérience comme Amazon, ajoutez un autocomplete miniature dans le header global. Composant components/HeaderSearch.tsx :
'use client';
import { useState, useEffect } from 'react';
import { MeiliSearch } from 'meilisearch';
import Link from 'next/link';
import { useDebouncedCallback } from 'use-debounce';
const client = new MeiliSearch({
host: process.env.NEXT_PUBLIC_MEILI_HOST!,
apiKey: process.env.NEXT_PUBLIC_MEILI_SEARCH_KEY!,
});
export default function HeaderSearch() {
const [q, setQ] = useState('');
const [results, setResults] = useState<any[]>([]);
const debouncedSearch = useDebouncedCallback(async (query: string) => {
if (!query) { setResults([]); return; }
const r = await client.index('products').search(query, { limit: 8 });
setResults(r.hits);
}, 250);
useEffect(() => debouncedSearch(q), [q]);
return (
<div className="relative">
<input value={q} onChange={(e) => setQ(e.target.value)}
placeholder="Rechercher..." className="border rounded px-3 py-2 w-72" />
{results.length > 0 && (
<ul className="absolute top-full bg-white shadow-lg w-full">
{results.map((h) => (
<li key={h.id} className="p-2 hover:bg-gray-100">
<Link href={`/produits/${h.slug}`}>{h.title}</Link>
</li>
))}
</ul>
)}
</div>
);
}
Étape 7 — Server-Side Rendering pour le SEO
Avec App Router et React Server Components, vous pouvez pre-render la première page de résultats côté serveur. Cela améliore le SEO et la latence perçue.
// app/recherche/[query]/page.tsx
import { MeiliSearch } from 'meilisearch';
const serverClient = new MeiliSearch({
host: process.env.MEILI_HOST!,
apiKey: process.env.MEILI_ADMIN_KEY!,
});
export default async function Page({ params }: { params: { query: string } }) {
const initial = await serverClient.index('products')
.search(decodeURIComponent(params.query), { limit: 20 });
return <ResultsClient initial={initial} query={params.query} />;
}
Étape 8 — Tracking analytics côté client
Trackez les recherches sans résultat pour améliorer la pertinence. Plausible self-hosted ou Umami suffisent :
useEffect(() => {
if (results.length === 0 && q.length > 2) {
plausible('NoResults', { props: { query: q } });
}
}, [results, q]);
Erreurs fréquentes
| Erreur | Cause | Solution |
|---|---|---|
| CORS error en local | Meilisearch refuse l’origine localhost | Configurer Caddy reverse proxy avec headers CORS adaptés |
| Hits sans surlignage | Composant Highlight mal configuré | Vérifier l’attribut attribute exact |
| Facettes vides | Attribut non filterable côté serveur | PATCH settings + waitForTask |
| Autocomplete laggy | Pas de debounce | useDebouncedCallback avec 250 ms |
| Master key dans le bundle | NEXT_PUBLIC_ sur la mauvaise clé | Régénérer une search-only et révoquer le master leaké |
| Pagination infinie cassée | routing=false | Activer routing dans InstantSearchNext |
Adaptation au contexte ouest-africain
Trois ajustements concrets. Mobile first : 75% des utilisateurs e-commerce africains naviguent sur mobile. Le design de la page recherche doit privilégier une seule colonne sur mobile, avec un bouton « Filtres » qui ouvre une drawer. Tester sur des Android 6 à 8 (encore courants au Mali, Burkina, Niger). Connexion intermittente : cacher localement les 50 derniers résultats avec localStorage, retourner le cache si offline. Le service worker peut intercepter les requêtes Meilisearch et servir un cache JSON. Données locales : pour un annuaire, utiliser des slugs descriptifs (/recherche/restaurants-dakar-plateau) plutôt que des paramètres URL pour le SEO Google et les partages WhatsApp.
Tutoriels frères du cluster
- Déployer Meilisearch sur Hetzner avec Coolify — le pré-requis serveur.
- Synchronisation Postgres → Meilisearch via Drizzle
FAQ
Pourquoi react-instantsearch et pas un client custom ? Pour gagner 2 à 3 jours de développement. La librairie gère pagination, routing URL, état partagé, et 25+ composants prêts. Custom client si vous avez des besoins très spécifiques.
Compatibilité Server Components ? Le composant InstantSearchNext de react-instantsearch-nextjs supporte le streaming et l’hydratation App Router depuis 2024.
Comment gérer plusieurs index dans une seule page ? Utiliser <Index indexName="..."/> imbriqué dans InstantSearchNext pour rechercher dans plusieurs index simultanément (federated search).
Performance sur mobile bas de gamme ? Le bundle react-instantsearch ajoute environ 50 Ko gzippé. Acceptable pour 99% des usages. Pour ultra-léger, écrire un client custom direct avec meilisearch-js seul (15 Ko).
Comment ajouter du tracking de clic sur résultat ? Composant Hit personnalisé qui appelle onClick avec l’ID du document, envoyé à Plausible/Umami pour analyser quels résultats convertissent réellement.
Pour aller plus loin
- 🔝 Retour au pilier : Guide complet Meilisearch 2026
- Documentation react-instantsearch : algolia.com/doc/api-reference/widgets/react
- Tutoriel suivant : Synchronisation Postgres avec Drizzle