ITSkillsCenter
تطوير الويب

دمج Meilisearch في Next.js: autocomplete، facets، instant search 2026

3 دقائق للقراءة

📍 المقالة الرئيسية: Meilisearch 2026: الدليل الكامل.

لديك Meilisearch في الإنتاج (راجع الدرس السابق) وتطبيق Next.js 15 مع App Router. هذا الدرس يربط الاثنين لتقديم تجربة بحث فورية تليق بـ Algolia: autocomplete من الحرف الثاني، فلاتر facet، تمييز المصطلحات، تصفح لا نهائي. الطريقة تعمل لمدونة، سوق إلكتروني، دليل، أو وثائق تقنية.

المتطلبات

قبل البدء، تأكد من توفر تطبيق Next.js 15 يعمل مع App Router. يجب أن يكون Meilisearch v1.10 متاحاً عبر HTTPS مع API key للقراءة فقط. ستحتاج إلى فهرس واحد على الأقل يحتوي على بضع مئات من الوثائق للاختبار. المستوى المطلوب: متوسط (React، hooks، TypeScript). الوقت المقدر: ساعة إلى ساعتين.

الخطوة 1 — تثبيت التبعيات

نبدأ بتثبيت حزم npm الضرورية. الحزمة @meilisearch/instant-meilisearch هي الجسر بين API الخاص بـ Meilisearch وبروتوكول InstantSearch (المتوافق مع Algolia)، مما يسمح بإعادة استخدام جميع مكونات react-instantsearch دون إعادة كتابة.

npm install meilisearch instantsearch.js react-instantsearch react-instantsearch-nextjs
npm install @meilisearch/instant-meilisearch

الخطوة 2 — متغيرات البيئة

في ملف .env.local، عرّف المتغيرات التالية. هام: فقط مفتاح search-only يجب أن يكون مسبوقاً بـ NEXT_PUBLIC_. master key يبقى من جانب الخادم فقط، في MEILI_MASTER_KEY دون البادئة العامة. هذا الفصل يحمي مفتاحك الإداري من التسرب في bundle JavaScript المُصدَّر للمتصفح.

NEXT_PUBLIC_MEILI_HOST=https://search.votre-entreprise.com
NEXT_PUBLIC_MEILI_SEARCH_KEY=votre-api-key-search-only

الخطوة 3 — إنشاء عميل InstantSearch

في ملف lib/search-client.ts، نُهيئ عميل InstantSearch ليتم استخدامه في جميع المكونات اللاحقة. نضع التوجيه 'use client' في الأعلى لأن هذا العميل يستهلك في React Client Components.

'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',
  }
);

الخطوة 4 — صفحة بحث كاملة

الآن ننشئ صفحة بحث كاملة في app/recherche/page.tsx. هذه الصفحة تجمع المكونات الأساسية: مربع البحث، مرشحات تنقيح حسب الفئة والعلامة التجارية، عرض النتائج مع تمييز المصطلحات، والترقيم. كل هذا يتم في مكون React Client واحد.

'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">
      <h3><Highlight attribute="title" hit={hit} /></h3>
      <p><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>
      <aside>
        <RefinementList attribute="category" />
        <RefinementList attribute="brand" />
      </aside>
      <main>
        <SearchBox />
        <Stats />
        <Hits hitComponent={Hit} />
        <Pagination />
      </main>
    </InstantSearchNext>
  );
}

الخطوة 5 — تكوين attributes من جانب Meilisearch

قبل أن تعمل الـ facets، يجب التصريح من جانب Meilisearch بالحقول القابلة للتصفية. هذه الإعدادات تحدد كيف يتعامل Meilisearch مع كل حقل: ما هو قابل للبحث، ما هو قابل للتصفية، ما هو قابل للترتيب. بدون هذه الإعدادات، الـ facets تعيد قوائم فارغة والترتيب لا يعمل.

curl -X PATCH 'https://search.votre-entreprise.com/indexes/products/settings' \
  -H 'Authorization: Bearer MASTER_KEY' \
  --data '{
    "filterableAttributes": ["category", "brand", "price"],
    "sortableAttributes": ["price", "created_at"],
    "searchableAttributes": ["title", "description", "brand"]
  }'

الخطوة 6 — Autocomplete في الـ header

لتجربة شبيهة بأمازون، أضف autocomplete مصغراً في الـ header العام. نستخدم useDebouncedCallback لتأخير الاستعلامات بـ 250 ميلي ثانية، مما يتجنب إرسال طلب لكل ضربة لوحة مفاتيح. هذا يحمي خادم Meilisearch من الإغراق ويوفر بيانات لمستخدمي 4G المحدودين.

'use client';
import { useState, useEffect } from 'react';
import { MeiliSearch } from 'meilisearch';
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 debounced = useDebouncedCallback(async (query: string) => {
    if (!query) { setResults([]); return; }
    const r = await client.index('products').search(query, { limit: 8 });
    setResults(r.hits);
  }, 250);
  
  useEffect(() => debounced(q), [q]);
  
  return (
    <div className="relative">
      <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Rechercher..." />
      {results.length > 0 && (
        <ul className="absolute bg-white shadow-lg">
          {results.map((h) => <li key={h.id}>{h.title}</li>)}
        </ul>
      )}
    </div>
  );
}

الخطوة 7 — Server-Side Rendering لـ SEO

مع App Router و React Server Components، يمكنك pre-render أول صفحة من النتائج من جانب الخادم. هذا يحسن SEO وزمن الاستجابة المُدرَك. المكوّن العميل يستلم النتائج الأولية مُهَيَّأة، ثم يأخذ التحكم لتفاعلات المستخدم اللاحقة.

// 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} />;
}

الأخطاء الشائعة

الخطأ السبب الحل
CORS error محلياً Meilisearch يرفض origin localhost تكوين Caddy reverse proxy مع رؤوس CORS مناسبة
Hits بدون تمييز مكوّن Highlight مكوَّن سيئاً التحقق من attribute الدقيق
Facettes فارغة attribut غير filterable من جانب الخادم PATCH settings + waitForTask
Autocomplete متأخر لا يوجد debounce useDebouncedCallback مع 250 مللي ثانية
Master key في bundle NEXT_PUBLIC_ على المفتاح الخاطئ إعادة توليد search-only وإلغاء master المسرَّب

التكيف مع السياق المغاربي وغرب إفريقيا

ثلاث تعديلات ملموسة. Mobile first: 75% من مستخدمي التجارة الإلكترونية الأفارقة يتصفحون على الهاتف. تصميم صفحة البحث يجب أن يفضل عمود واحد على الهاتف، مع زر «فلاتر» يفتح drawer. اختبر على Android 6 إلى 8 (لا يزال شائعاً في مالي وبوركينا والنيجر). اتصال متقطع: قم بتخزين آخر 50 نتيجة محلياً مع localStorage، أعد cache إذا كنت دون اتصال. بيانات محلية: للدليل، استخدم slugs وصفية (/recherche/restaurants-dakar-plateau) بدلاً من معاملات URL لـ SEO Google ومشاركات WhatsApp.

دروس الإخوة في المجموعة

الأسئلة المتكررة

لماذا react-instantsearch وليس عميل مخصص؟ لكسب 2 إلى 3 أيام من التطوير. المكتبة تدير الترقيم، توجيه URL، الحالة المشتركة، و25+ مكوناً جاهزاً.

توافق Server Components؟ مكون InstantSearchNext الخاص بـ react-instantsearch-nextjs يدعم streaming وتحديث App Router منذ 2024.

الأداء على هاتف منخفض الجودة؟ bundle react-instantsearch يضيف حوالي 50 كيلوبايت gzipped. مقبول لـ 99% من الاستخدامات.

للاستزادة

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é