ITSkillsCenter
Développement Web

Intégrer Meilisearch dans Next.js : autocomplete, facets, instant search 2026

6 min de lecture

📍 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

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

Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 250.000 FCFA
Parlons de Votre Projet
Publicité