Автодоповнення пошуку для веб-застосунків: реалізація та компоненти

Реалізація пошуку з автодоповненням для веб-застосунку

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Автодоповнення пошуку для веб-застосунків: реалізація та компоненти
Середній
~2-3 дні

Наші компетенції:

Часті запитання

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1419
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1287
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    983
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1246
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    983
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    998

Реалізація пошуку з автодоповненням для веб-застосунку

Нещодавно до нас звернувся інтернет-магазин з каталогом у 200 тис. товарів: користувачі скаржилися на повільний пошук, а стандартне рішення на WordPress не справлялося. Ми реалізували автодоповнення на основі Elasticsearch з fuzzy-пошуком та кешуванням — час введення скоротився в 3 рази, а навантаження на сервер впало на 60%. Технічно автодоповнення — це перетин кількох задач: швидкий fuzzy-пошук по індексу, debounce, щоб не перевантажувати сервер, коректна робота з клавіатурою (ARIA combobox), скасування застарілих запитів. Кожна вирішується окремо, разом вони складають повноцінний компонент. Ми реалізували десятки проектів з автодоповненням, тому знаємо типові граблі.

Технічні складнощі

На перший погляд здається: зробив <input> з випадним списком — і готово. Але на практиці потрібні:

  • Debounce зі скасуванням попередніх запитів (щоб не завалювати сервер). Оптимальна затримка — 250 мс.
  • Fuzzy-пошук (пошук з неточним збігом) для стійкості до помилок. Наприклад, запит "айфон" знайде "iPhone".
  • Коректне керування з клавіатури (стрілки, Enter, Escape) та ARIA-атрибути для доступності.
  • Підсвітка збігів у результатах, щоб користувач бачив, що його запит враховано.
  • Кешування частих запитів для зниження latency. Типовий кеш — 100 записів з TTL 30 секунд.

Порівняння підходів: client-side, server-side та гібридний

Підхід Обсяг даних Час відгуку Складність реалізації Типові бібліотеки
Client-side до ~50 000 записів Миттєвий (немає мережевих затримок) Низька Fuse.js, MiniSearch
Server-side будь-який Залежить від швидкості бекенду та мережі Висока Elasticsearch, Typesense, PostgreSQL pg_trgm
Гібридний будь-який Швидкий для частих запитів, для рідкісних — як server-side Середня Будь-який з вище + кеш на клієнті

Client-side пошук працює в 5–10 разів швидше server-side для наборів до 50 тис. записів. Вибір підходу критичний для UX та бюджету на інфраструктуру.

Як вибрати підхід: client-side чи server-side?

Якщо у вас менше 50 000 записів і дані рідко оновлюються, client-side дасть мінімальну затримку та зменшить навантаження на сервер. Для великих каталогів або динамічних даних потрібен server-side з кешуванням на клієнті. Ми допомагаємо визначити оптимальний варіант на етапі аудиту.

Що входить у роботу

При замовленні автодоповнення ви отримуєте:

  • Архітектурне рішення: вибір підходу на основі обсягу даних та вимог до швидкості.
  • Готовий компонент з ARIA combobox, керуванням з клавіатури та підсвіткою.
  • Серверну частину: індекс Elasticsearch або SQL-функції з pg_trgm.
  • Кешування на клієнті (TTL 30 секунд, до 100 записів) та оптимізацію запитів.
  • Документацію з інтеграції та розгортання.
  • Тестування на мобільних пристроях та slow network.

Весь проект покривається гарантією на 6 місяців — якщо щось зламалося, ми лагодимо безкоштовно протягом 24 годин.

Як відбувається реалізація автодоповнення?

Процес складається з чотирьох етапів:

  1. Аналіз даних та вимог: оцінюємо обсяг, частоту оновлень, latency-цілі.
  2. Проектування архітектури: вибираємо підхід (client-side, server-side, гібридний), визначаємо індекс.
  3. Розробка компонента: реалізуємо клієнтську частину з debounce (затримка 250 мс), скасуванням запитів, ARIA combobox.
  4. Інтеграція та тестування: підключаємо серверну частину, налаштовуємо кеш, перевіряємо на мобільних та slow network.

Кожен етап завершується код-рев'ю та unit-тестами.

Реалізація клієнтської частини

Базовий хук з debounce та скасуванням

import { useState, useEffect, useRef, useCallback } from 'react'; type SearchResult = { id: string; title: string; category?: string; url: string; }; function useAutocomplete( fetchFn: (query: string, signal: AbortSignal) => Promise<SearchResult[]>, delay = 250 ) { const [query, setQuery] = useState(''); const [results, setResults] = useState<SearchResult[]>([]); const [loading, setLoading] = useState(false); const [error, setError] = useState<Error | null>(null); const abortRef = useRef<AbortController | null>(null); const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null); const search = useCallback((value: string) => { setQuery(value); if (timerRef.current) clearTimeout(timerRef.current); if (abortRef.current) abortRef.current.abort(); if (value.trim().length < 2) { setResults([]); return; } timerRef.current = setTimeout(async () => { const controller = new AbortController(); abortRef.current = controller; setLoading(true); setError(null); try { const data = await fetchFn(value, controller.signal); if (!controller.signal.aborted) setResults(data); } catch (err) { if (err instanceof Error && err.name !== 'AbortError') setError(err); } finally { if (!controller.signal.aborted) setLoading(false); } }, delay); }, [fetchFn, delay]); useEffect(() => () => { if (timerRef.current) clearTimeout(timerRef.current); if (abortRef.current) abortRef.current.abort(); }, []); return { query, results, loading, error, search }; } 

React autocomplete компонент з ARIA combobox Коректна реалізація згідно з ARIA-патерном combobox:

import { useId, useRef, useState } from 'react'; interface AutocompleteProps { placeholder?: string; onSelect: (result: SearchResult) => void; fetchResults: (query: string, signal: AbortSignal) => Promise<SearchResult[]>; } export function Autocomplete({ placeholder, onSelect, fetchResults }: AutocompleteProps) { const id = useId(); const listId = `${id}-listbox`; const inputRef = useRef<HTMLInputElement>(null); const listRef = useRef<HTMLUListElement>(null); const { query, results, loading, search } = useAutocomplete(fetchResults); const [activeIndex, setActiveIndex] = useState(-1); const [open, setOpen] = useState(false); const isOpen = open && (results.length > 0 || loading); const handleKeyDown = (e: React.KeyboardEvent) => { switch (e.key) { case 'ArrowDown': e.preventDefault(); setActiveIndex((i) => Math.min(i + 1, results.length - 1)); break; case 'ArrowUp': e.preventDefault(); setActiveIndex((i) => Math.max(i - 1, -1)); break; case 'Enter': if (activeIndex >= 0 && results[activeIndex]) { onSelect(results[activeIndex]); setOpen(false); setActiveIndex(-1); } break; case 'Escape': setOpen(false); setActiveIndex(-1); inputRef.current?.focus(); break; } }; return ( <div className="autocomplete" role="combobox" aria-expanded={isOpen} aria-haspopup="listbox"> <input ref={inputRef} type="search" placeholder={placeholder} value={query} aria-autocomplete="list" aria-controls={listId} aria-activedescendant={activeIndex >= 0 ? `${id}-option-${activeIndex}` : undefined} onChange={(e) => { search(e.target.value); setOpen(true); setActiveIndex(-1); }} onFocus={() => query.length >= 2 && setOpen(true)} onBlur={() => setTimeout(() => setOpen(false), 150)} onKeyDown={handleKeyDown} /> {isOpen && ( <ul ref={listRef} id={listId} role="listbox" className="autocomplete__dropdown"> {loading && <li role="option" aria-selected="false" className="autocomplete__loading">Пошук...</li>} {results.map((result, index) => ( <li key={result.id} id={`${id}-option-${index}`} role="option" aria-selected={index === activeIndex} className={`autocomplete__option ${index === activeIndex ? 'autocomplete__option--active' : ''}`} onMouseDown={() => { onSelect(result); setOpen(false); }} onMouseEnter={() => setActiveIndex(index)} > <span> {(() => { if (!query.trim()) return <span>{result.title}</span>; const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const parts = result.title.split(new RegExp(`(${escaped})`, 'gi')); return parts.map((part, i) => part.toLowerCase() === query.toLowerCase() ? <mark key={i}>{part}</mark> : <span key={i}>{part}</span> ); })()} </span> {result.category && <span className="autocomplete__category">{result.category}</span>} </li> ))} </ul> )} </div> ); } 
Чому debounce 250 мс? Значення 250 мс — компроміс між чутливістю та навантаженням. Менше 150 мс — надто часті запити, більше 400 мс — користувач помічає затримку.

Серверна частина на Elasticsearch

Використовуємо Elasticsearch suggest API:

// POST /api/suggest async function suggestHandler(req: Request) { const { q } = await req.json(); if (!q || q.length < 2) return Response.json({ hits: [] }); const response = await esClient.search({ index: 'products', body: { suggest: { title_suggest: { prefix: q, completion: { field: 'title.suggest', size: 10, fuzzy: { fuzziness: 'AUTO' }, }, }, }, query: { multi_match: { query: q, fields: ['title^3', 'description', 'tags^2'], type: 'bool_prefix', }, }, _source: ['id', 'title', 'category', 'url', 'image'], size: 10, }, }); return Response.json({ hits: response.hits.hits.map((h) => h._source) }); } 

Індекс з полем completion:

{ "mappings": { "properties": { "title": { "type": "text", "fields": { "suggest": { "type": "completion", "analyzer": "standard" }, "keyword": { "type": "keyword" } } } } } } 

Кешування на стороні клієнта

const cache = new Map<string, { data: SearchResult[]; ts: number }>(); const TTL = 30_000; // 30 секунд async function fetchWithCache(query: string, signal: AbortSignal) { const cached = cache.get(query); if (cached && Date.now() - cached.ts < TTL) return cached.data; const res = await fetch(`/api/suggest?q=${encodeURIComponent(query)}`, { signal }); const data = await res.json(); cache.set(query, { data: data.hits, ts: Date.now() }); if (cache.size > 100) cache.delete(cache.keys().next().value); return data.hits; } 

Орієнтовні терміни та гарантія

Етап Час
Просте автодоповнення (fetch + debounce, без ARIA) 4–8 годин
Повноцінний компонент з ARIA, підсвіткою та кешуванням 2–3 дні
Додавання серверного індексу Elasticsearch +1–2 дні

Вартість розраховується індивідуально під ваш проект. Ми надаємо гарантію 6 місяців на реалізований функціонал — якщо баг проявився, виправляємо протягом 24 годин. Наш багаторічний досвід у веб-розробці та десятки виконаних проектів — гарантія якості. Кожне рішення проходить код-рев'ю, тестування на швидкість та доступність.

Замовте реалізацію автодоповнення для вашого сайту — ми підберемо оптимальне рішення. Отримайте консультацію по вашому проекту, напишіть нам.