Інтеграція Algolia для пошуку на сайті
Algolia — пошуковий SaaS з власними серверами індексації та пошуком із толерантністю до опечаток. Замість написання повнотекстового пошуку на PostgreSQL або Elasticsearch використовуйте готовий індекс, API та компоненти InstantSearch для фронтенду. Основна робота — налаштувати синхронізацію даних та конфігурувати релевантність.
Як працює Algolia
Дані завантажуються в індекс (аналог таблиці) як об'єкти JSON з атрибутами. Пошук виконується на налаштованих полях з урахуванням опечаток, синонімів та ваг. Кожен об'єкт повинен мати унікальний objectID.
Обмеження безплатного тарифу: 10 000 записів, 10 000 пошукових запитів на місяць. Достатньо для малих сайтів.
Установка та індексація
composer require algolia/algoliasearch-client-php
npm install algoliasearch instantsearch.js
# або для React:
npm install algoliasearch react-instantsearch
Простої індексації через PHP SDK:
use Algolia\AlgoliaSearch\SearchClient;
$client = SearchClient::create(
config('algolia.app_id'),
config('algolia.admin_api_key') // Ключ адміна — тільки на backend!
);
$index = $client->initIndex('products');
// Індексація одного об'єкту
$index->saveObject([
'objectID' => 'product-' . $product->id,
'name' => $product->name,
'description' => strip_tags($product->description),
'category' => $product->category->name,
'price' => $product->price,
'in_stock' => $product->stock > 0,
'image_url' => $product->thumbnail_url,
'slug' => $product->slug,
]);
// Пакетна індексація
$objects = Product::with('category')->get()->map(fn($p) => [
'objectID' => 'product-' . $p->id,
'name' => $p->name,
'category' => $p->category->name,
'price' => $p->price,
'in_stock' => $p->stock > 0,
])->toArray();
$index->saveObjects($objects);
Синхронізація через Observer
Індекс повинен оновлюватися при зміні даних. Observer у Laravel:
class ProductObserver {
private SearchClient $algolia;
public function __construct() {
$this->algolia = SearchClient::create(
config('algolia.app_id'),
config('algolia.admin_api_key')
);
}
public function saved(Product $product): void {
$this->algolia->initIndex('products')->saveObject(
$this->toAlgoliaRecord($product)
);
}
public function deleted(Product $product): void {
$this->algolia->initIndex('products')->deleteObject('product-' . $product->id);
}
private function toAlgoliaRecord(Product $product): array {
return [
'objectID' => 'product-' . $product->id,
'name' => $product->name,
'description' => Str::limit(strip_tags($product->description), 300),
'category' => $product->category->name,
'tags' => $product->tags->pluck('name')->toArray(),
'price' => (float)$product->price,
'in_stock' => $product->stock > 0,
'image_url' => $product->thumbnail_url,
'slug' => $product->slug,
'updated_at' => $product->updated_at->timestamp,
];
}
}
Зареєструйте в AppServiceProvider:
Product::observe(ProductObserver::class);
Конфігурація індексу: релевантність та фільтри
Налаштуйте атрибути для пошуку та фільтрації через Dashboard або API:
$index->setSettings([
// Поля для пошуку (порядок = пріоритет)
'searchableAttributes' => [
'name',
'unordered(category)',
'unordered(description)',
'unordered(tags)',
],
// Атрибути для фільтрації та фасетів
'attributesForFaceting' => [
'filterOnly(price)',
'category',
'in_stock',
],
// Що повертати в результатах (виключити важкі поля)
'attributesToRetrieve' => [
'objectID', 'name', 'price', 'category', 'image_url', 'slug', 'in_stock',
],
// Підсвітлення збігів
'attributesToHighlight' => ['name', 'category'],
// Толерантність до опечаток
'typoTolerance' => true,
'minWordSizefor1Typo' => 4,
'minWordSizefor2Typos' => 8,
// Ранжування
'ranking' => [
'typo', 'geo', 'words', 'filters', 'proximity', 'attribute',
'exact', 'custom',
],
'customRanking' => ['desc(updated_at)'],
]);
React-компонент пошуку
import algoliasearch from 'algoliasearch/lite';
import {
InstantSearch,
SearchBox,
Hits,
RefinementList,
Pagination,
Highlight,
Configure,
} from 'react-instantsearch';
const searchClient = algoliasearch(
import.meta.env.VITE_ALGOLIA_APP_ID,
import.meta.env.VITE_ALGOLIA_SEARCH_KEY // Ключ тільки для пошуку, публічний
);
const ProductHit: React.FC<{ hit: ProductRecord }> = ({ hit }) => (
<a href={`/products/${hit.slug}`} className="flex gap-3 p-3 hover:bg-gray-50 rounded">
<img src={hit.image_url} alt={hit.name} className="w-12 h-12 object-cover rounded" />
<div>
<p className="font-medium">
<Highlight attribute="name" hit={hit} />
</p>
<p className="text-sm text-gray-500">
<Highlight attribute="category" hit={hit} />
</p>
<p className="font-semibold">{hit.price.toLocaleString()} ₽</p>
</div>
</a>
);
export const SiteSearch: React.FC = () => (
<InstantSearch searchClient={searchClient} indexName="products">
<Configure hitsPerPage={20} filters="in_stock:true" />
<SearchBox
placeholder="Пошук товарів..."
classNames={{ input: 'w-full border rounded-lg px-4 py-2' }}
/>
<div className="flex gap-6 mt-4">
<aside className="w-48 shrink-0">
<p className="font-medium mb-2">Категорія</p>
<RefinementList attribute="category" />
</aside>
<div className="flex-1">
<Hits hitComponent={ProductHit} />
<Pagination className="mt-4" />
</div>
</div>
</InstantSearch>
);
Пошук у модальному вікні (Command Palette)
Для швидкого доступу реалізуйте пошук у наслідком без переходу на сторінку результатів:
import { useSearchBox, useHits } from 'react-instantsearch';
const CommandSearch: React.FC<{ onSelect: (hit: any) => void }> = ({ onSelect }) => {
const { query, refine } = useSearchBox();
const { hits } = useHits();
return (
<div className="fixed inset-0 bg-black/50 z-50 flex items-start justify-center pt-20">
<div className="bg-white rounded-xl shadow-2xl w-full max-w-xl overflow-hidden">
<input
autoFocus
value={query}
onChange={e => refine(e.target.value)}
placeholder="Пошук..."
className="w-full px-4 py-3 text-lg outline-none border-b"
/>
<div className="max-h-96 overflow-y-auto">
{hits.map(hit => (
<button key={hit.objectID} onClick={() => onSelect(hit)}
className="w-full text-left px-4 py-2 hover:bg-blue-50">
{hit.name}
</button>
))}
</div>
</div>
</div>
);
};
Аналітика пошуку
Algolia збирає клики та конверсії для поліпшення релевантності. Включіть це:
import { createInsightsMiddleware } from 'instantsearch.js/es/middlewares';
import aa from 'search-insights';
aa('init', {
appId: import.meta.env.VITE_ALGOLIA_APP_ID,
apiKey: import.meta.env.VITE_ALGOLIA_SEARCH_KEY,
useCookie: true,
});
// У InstantSearch
<InstantSearch
searchClient={searchClient}
indexName="products"
middlewares={[createInsightsMiddleware({ insightsClient: aa })]}
insights
>
Після цього Algolia починає збирати дані про те, за якими запитами клацають, та автоматично поліпшує ранжування.
Терміни реалізації
Базова інтеграція з індексацією через Observer та пошуком на фронтенді: 1–2 дні. Додавання фасетної фільтрації, Command Palette, аналітики та налаштування релевантності: 3–4 дні. Багатомовні індекси (окремий індекс для кожної мови) з синхронізацією: плюс 1–2 дні.







