Розробка блоку «Нещодавно переглянуті» для інтернет-магазину

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

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

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

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка блоку «Нещодавно переглянуті» для інтернет-магазину
Проста
~1 робочий день
Часті питання

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

Етапи розробки

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

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

Розробка блоку "Недавно переглянуті" для E-Commerce

Блок недавно переглянутих товарів допомагає користувачу повернутися до вивчених позицій без пошуку по каталогу. Це один із найпростіших елементів персоналізації з мінімальною вартістю розробки та вимірюваним впливом на повернення до карточок товару. Розробка займає 1–2 робочих дня.

Зберігання історії переглядів

Для гостей історія зберігається в localStorage. Для авторизованих — за бажанням синхронізується з сервером:

// hooks/useRecentlyViewed.ts
const STORAGE_KEY = 'recently_viewed';
const MAX_ITEMS = 20;

export const useRecentlyViewed = () => {
  const [items, setItems] = useState<number[]>(() => {
    const stored = localStorage.getItem(STORAGE_KEY);
    return stored ? JSON.parse(stored) : [];
  });

  const addProduct = useCallback((productId: number) => {
    setItems(prev => {
      const filtered = prev.filter(id => id !== productId);
      const updated = [productId, ...filtered].slice(0, MAX_ITEMS);
      localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
      return updated;
    });
  }, []);

  const clearHistory = useCallback(() => {
    localStorage.removeItem(STORAGE_KEY);
    setItems([]);
  }, []);

  return { productIds: items, addProduct, clearHistory };
};

На сторінці кожного товару — виклик addProduct(product.id):

// У компоненті сторінки товару
const { addProduct } = useRecentlyViewed();
useEffect(() => { addProduct(product.id); }, [product.id]);

Серверна синхронізація для авторизованих користувачів

Для авторизованих користувачів історія синхронізується з сервером — щоб зберігалася між пристроями:

// routes
Route::middleware('auth:sanctum')->post('/me/recently-viewed', [RecentlyViewedController::class, 'sync']);
Route::middleware('auth:sanctum')->get('/me/recently-viewed', [RecentlyViewedController::class, 'index']);
public function sync(Request $request): JsonResponse
{
    $request->validate(['product_ids' => 'required|array|max:20', 'product_ids.*' => 'integer']);

    $user = $request->user();
    // Оновлюємо порядок: передана список — актуальний стан
    $user->recentlyViewed()->sync(
        collect($request->product_ids)->mapWithKeys(fn($id, $pos) => [
            $id => ['position' => $pos, 'viewed_at' => now()]
        ])
    );

    return response()->json(['synced' => count($request->product_ids)]);
}

Синхронізація — при логіні (мерж localStorage з серверною історією) та при закритті вкладки (beforeunload + navigator.sendBeacon).

Завантаження даних про товари

Історія зберігає тільки product_id. Для відображення блоку потрібні дані — один запит до API:

const RecentlyViewedBlock = () => {
  const { productIds } = useRecentlyViewed();
  const visibleIds = productIds.slice(0, 8); // показуємо не більше 8

  const { data: products } = useQuery({
    queryKey: ['recently-viewed-products', visibleIds],
    queryFn: () => api.get('/products/batch', { params: { ids: visibleIds.join(',') } }),
    enabled: visibleIds.length > 0,
    staleTime: 300_000,
  });

  if (!products?.length) return null;

  // Зберігаємо порядок з історії
  const ordered = visibleIds
    .map(id => products.find((p: Product) => p.id === id))
    .filter(Boolean);

  return (
    <section>
      <div className="flex justify-between items-center mb-4">
        <h2 className="text-lg font-semibold">Ви недавно переглядали</h2>
        <button onClick={clearHistory} className="text-sm text-gray-400 hover:text-gray-600">
          Очистити історію
        </button>
      </div>
      <ProductCarousel products={ordered} />
    </section>
  );
};

Endpoint для batch-завантаження

public function batch(Request $request): JsonResponse
{
    $request->validate(['ids' => 'required|string']);
    $ids = array_filter(array_map('intval', explode(',', $request->ids)));
    $ids = array_slice($ids, 0, 20); // лімітувати

    $products = Product::whereIn('id', $ids)
        ->where('is_active', true)
        ->select(['id', 'name', 'slug', 'price', 'sale_price', 'rating_avg', 'rating_count'])
        ->with('thumbnail')
        ->get()
        ->keyBy('id');

    // Повертаємо у порядку переданих id
    $ordered = collect($ids)->map(fn($id) => $products->get($id))->filter()->values();

    return response()->json(ProductCardResource::collection($ordered));
}

Де розміщувати блок

  • Головна сторінка: для повернення користувачів — замість або поряд з популярними товарами
  • Сторінка категорії: у нижній частині, після основної сітки товарів
  • Сторінка товару: під блоком "Схожі товари"
  • Кошик: в боковій панелі на десктопі
  • Порожні результати пошуку: "Можливо, ви шукаєте щось з переглянутого?"

Виключення поточного товару

На сторінці товару X з блоку "Недавно переглянуті" виключається сам товар X — інакше він неминуче опиниться першим у списку:

const productIds = useRecentlyViewed().productIds.filter(id => id !== currentProductId);

Приватність

Кнопка "Очистити історію" дає користувачу контроль над даними. Термін зберігання в localStorage — не обмежений браузером, але можна додати TTL вручну:

const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
  const { items, savedAt } = JSON.parse(stored);
  const isExpired = Date.now() - savedAt > 30 * 24 * 60 * 60 * 1000; // 30 днів
  if (isExpired) localStorage.removeItem(STORAGE_KEY);
}