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

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, 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

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

Блок недавно просмотренных товаров помогает пользователю вернуться к изученным позициям без поиска по каталогу. Это один из простейших элементов персонализации с минимальной стоимостью разработки и измеримым эффектом на возвраты к карточкам товара. Разработка занимает 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);
}