Перейти к основному содержимому

Успешно пройти собеседование по System Design / Проектируем Amazon

· 51 мин. чтения

Сегодня мы разберём систем-дизайн интервью для frontend-разработчика, на котором кандидат проектирует каталог e-commerce маркетплейса — от сбора требований и проектирования UI до выбора технологического стека (React, Next.js, Zustand), архитектуры (Feature-Sliced Design), оптимизаций производительности и обеспечения доступности (accessibility). Это демонстрирует полный спектр компетенций, которые ожидают от фронтенд-кандидата на собеседовании в крупные компании.

Вопрос 1. Какую систему предлагается спроектировать в рамках собеседования?

Таймкод: 00:00:30

Ответ собеседника: Правильный. Предлагается спроектировать e-commerce marketplace, подобный Озону, Амазону или Wildberries.

Правильный ответ:

В рамках системного проектирования предлагается спроектировать высоконагруженный e-commerce marketplace — маркетплейс, аналогичный по масштабу Ozon, Amazon или Wildberries. Это платформа, на которой множество продавцов (sellers) размещают свои товары, а покупатели (buyers) могут искать, сравнивать, заказывать и получать эти товары.

Ключевые аспекты, которые необходимо охватить при проектировании:

  • Мультитенантность — множество продавцов работают на одной платформе, данные изолированы.
  • Каталог товаров — хранение, поиск и фильтрация миллионов SKU.
  • Поиск и рекомендации — полнотекстовый поиск, фасеты, персонализация.
  • Корзина и оформление заказа — управление состоянием заказа, оркестрация оплаты и резервирования товаров.
  • Оплата и расчёты — интеграция с платёжными провайдерами, холдирование, выплаты продавцам.
  • Логистика и доставка — управление складами, интеграция с курьерскими службами, отслеживание.
  • Масштабируемость и отказоустойчивость — система должна выдерживать пиковые нагрузки (распродажи, чёрная пятница).
  • Мониторинг и observability — логи, метрики, трейсинг.

Такой проект позволяет оценить навыки кандидата в области распределённых систем, проектирования API, выбора хранилищ данных, паттернов микросервисной архитектуры и принятия инженерных компромиссов.

Вопрос 2. Какие основные функциональные элементы включает типичный маркетплейс и что конкретно будет реализовано в рамках задачи?

Таймкод: 00:00:42

Ответ собеседника: Правильный. Типичный маркетплейс включает ленту заказов, личный профиль, рекомендации, магазины. В рамках задачи основной фокус — каталог с просмотром карточки товара, фильтрацией, сортировкой, поиском и пагинацией.

Правильный ответ:

Полный функционал типичного маркетплейса:

  • Каталог товаров — дерево категорий, карточки товаров, медиа (фото, видео), характеристики, цены, наличие.
  • Поиск и навигация — полнотекстовый поиск, фасетная фильтрация, сортировка, пагинация, подсказки (autocomplete).
  • Корзина — добавление/удаление товаров, изменение количества, промокоды.
  • Оформление заказа (checkout) — выбор адреса доставки, способа оплаты, подтверждение заказа.
  • Оплата — интеграция с платёжными шлюзами, холдирование средств, возвраты.
  • Личный кабинет покупателя — история заказов, избранное, отзывы, возвраты.
  • Личный кабинет продавца — управление товарами (CRUD), аналитика продаж, управление ценами и остатками.
  • Рекомендательная система — «вам также понравится», «похожие товары», персонализированные ленты.
  • Система отзывов и рейтингов — оценки, текстовые отзывы, модерация.
  • Административная панель — модерация контента, управление пользователями, аналитика платформы.
  • Уведомления — email, push, SMS о статусе заказа, акциях.

В рамках данного задания фокус на каталоге:

Основной объект проектирования — каталог товаров со следующими возможностями:

  • Просмотр карточки товара (детальная страница с описанием, фото, ценой, характеристиками).
  • Фасетная фильтрация по категориям, брендам, ценовому диапазону, рейтингу и другим атрибутам.
  • Сортировка по цене, популярности, рейтингу, новизне.
  • Полнотекстовый поиск с учётом морфологии и релевантности.
  • Пагинация (offset-based и/или cursor-based).
  • Высокая производительность при большом объёме данных (миллионы SKU).

Это ядро маркетплейса, от которого зависит пользовательский опыт и конверсия.

Вопрос 3. Какие фильтры будут реализованы в каталоге?

Таймкод: 00:01:15

Ответ собеседника: Правильный. Будут реализованы два фильтра: наличие товара на складе и наличие скидки на товар.

Правильный ответ:

В рамках данного задания в каталоге предусмотрены два основных фильтра:

1. Наличие товара на складе (in stock)

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

На уровне данных это может быть булево поле in_stock или вычисляемое поле на основе таблицы остатков:

-- Пример схемы хранения остатков
CREATE TABLE product_stock (
product_id BIGINT NOT NULL,
warehouse_id BIGINT NOT NULL,
quantity INT NOT NULL DEFAULT 0,
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
PRIMARY KEY (product_id, warehouse_id)
);

-- Запрос товаров в наличии
SELECT p.*
FROM products p
JOIN product_stock ps ON p.id = ps.product_id
WHERE ps.quantity > 0;

2. Наличие скидки (has discount)

Фильтр показывает товары с действующей скидкой — где текущая цена отличается от базовой. Это стимулирует продажи и привлекает внимание пользователей, ищущих выгодные предложения.

-- Пример схемы цен и скидок
CREATE TABLE product_prices (
product_id BIGINT NOT NULL,
base_price DECIMAL(10, 2) NOT NULL,
sale_price DECIMAL(10, 2),
discount_from TIMESTAMP,
discount_to TIMESTAMP,
PRIMARY KEY (product_id)
);

-- Запрос товаров со скидкой
SELECT *
FROM product_prices
WHERE sale_price IS NOT NULL
AND sale_price < base_price
AND NOW() BETWEEN discount_from AND discount_to;

Примечание о расширеемости:

Хотя в рамках задания реализуются только два фильтра, архитектура должна закладывать возможность добавления фасетных фильтров по категориям, брендам, ценовым диапазонам, рейтингу и другим атрибутам без существенных переработок.

Вопрос 4. Будет ли реализован функционал добавления товаров в избранное и откуда он будет доступен?

Таймкод: 00:01:33

Ответ собеседника: Правильный. Да, будет реализована возможность добавлять товары в избранное как с карточки товара, так и из каталога.

Правильный ответ:

Да, функционал добавления товаров в избранное (wishlist / favorites) предусмотрен. Доступ к нему будет реализован из двух точек входа:

1. Из карточки товара (Product Detail Page)

На детальной странице товара пользователь может добавить конкретный товар в избранное. Это основной сценарий — пользователь просматривает товар и решает сохранить его для будущего сравнения или покупки.

2. Из каталога (список товаров)

В листинге каталога, рядом с каждым товаром (или при наведении), размещается кнопка-иконка (сердечко/звезда), позволяющая добавить товар в избранное без перехода на карточку. Это ускоряет взаимодействие и улучшает UX.

Пример API для управления избранным:

// POST /api/v1/favorites
type AddToFavoritesRequest struct {
ProductID int64 `json:"product_id" validate:"required"`
}

// DELETE /api/v1/favorites/{product_id}
// GET /api/v1/favorites — получить список избранных товаров

Пример схемы хранения:

CREATE TABLE user_favorites (
user_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
PRIMARY KEY (user_id, product_id),
FOREIGN KEY (product_id) REFERENCES products(id)
);

CREATE INDEX idx_favorites_product ON user_favorites(product_id);

Важные аспекты реализации:

  • Идемпотентность — повторное добавление одного и того же товара не должно создавать дубликатов (UPSERT).
  • Аутентификация — избранное привязано к конкретному пользователю.
  • Удаление — возможность убрать товар из избранного (toggle-поведение).
  • Пагинация — список избранных товаров должен поддерживать пагинацию при большом количестве сохранённых позиций.

Вопрос 5. Какие поля у продукта и какие действия доступны с карточки товара?

Таймкод: 00:01:44

Ответ собеседника: Правильный. У продукта есть картинка, название, описание, цена, скидка, иконка добавления в избранное. Также должна быть возможность добавить товар в корзину, в том числе несколько единиц одного товара.

Правильный ответ:

Поля продукта (карточка товара):

ПолеОписаниеТип
idУникальный идентификатор товараBIGINT
nameНазвание товараVARCHAR(512)
descriptionПодробное описание товараTEXT
image_urlСсылка на главное изображениеVARCHAR(1024)
base_priceБазовая цена без скидкиDECIMAL(10,2)
sale_priceЦена со скидкой (если есть)DECIMAL(10,2), NULLABLE
discount_percentПроцент скидки (вычисляемый)INT
in_stockФлаг наличия на складеBOOLEAN
ratingСредний рейтинг товараDECIMAL(3,2)
reviews_countКоличество отзывовINT
category_idСсылка на категориюBIGINT
brandБренд товараVARCHAR(256)

Действия, доступные с карточки товара:

  • Добавить в корзину — с возможностью выбора количества единиц (quantity selector). Пользователь указывает количество и нажимает «В корзину».
  • Добавить в избранное — toggle-кнопка (сердечко) для сохранения товара в wishlist.
  • Просмотреть изображения — галерея фотографий (если их несколько).
  • Посмотреть характеристики — развёрнутые атрибуты товара.
  • Посмотреть отзывы — рейтинг и текстовые отзывы других покупателей.

Пример структуры в Go:

type Product struct {
ID int64 `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Description string `json:"description" db:"description"`
ImageURL string `json:"image_url" db:"image_url"`
BasePrice float64 `json:"base_price" db:"base_price"`
SalePrice *float64 `json:"sale_price,omitempty" db:"sale_price"`
InStock bool `json:"in_stock" db:"in_stock"`
Rating float64 `json:"rating" db:"rating"`
ReviewsCount int `json:"reviews_count" db:"reviews_count"`
Brand string `json:"brand" db:"brand"`
}

type AddToCartRequest struct {
ProductID int64 `json:"product_id" validate:"required,min=1"`
Quantity int `json:"quantity" validate:"required,min=1,max=99"`
}

Пример SQL-таблицы:

CREATE TABLE products (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(512) NOT NULL,
description TEXT,
image_url VARCHAR(1024),
base_price DECIMAL(10, 2) NOT NULL CHECK (base_price >= 0),
sale_price DECIMAL(10, 2) CHECK (sale_price IS NULL OR sale_price >= 0),
in_stock BOOLEAN NOT NULL DEFAULT false,
rating DECIMAL(3, 2) DEFAULT 0 CHECK (rating BETWEEN 0 AND 5),
reviews_count INT DEFAULT 0,
brand VARCHAR(256),
category_id BIGINT REFERENCES categories(id),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_products_category ON products(category_id);
CREATE INDEX idx_products_brand ON products(brand);
CREATE INDEX idx_products_in_stock ON products(in_stock);

Вопрос 6. Будет ли персонализированная лента рекомендаций в рамках каталога?

Таймкод: 00:02:09

Ответ собеседния: Правильный. В рамках каталога персонализированной ленты не будет, но учитывается, что в системе она может быть, например, на главной странице.

Правильный ответ:

В рамках текущего задания — проектирования каталога товаров — персонализированная лента рекомендаций не реализуется. Каталог представляет собой общий набор товаров, доступный всем пользователям одинаково, с возможностью фильтрации, сортировки и поиска.

Архитектурное разделение:

  • Каталог — универсальное представление товаров без персонализации. Все пользователи видят одни и те же товары при одинаковых фильтрах и сортировке.
  • Главная страница / Личный кабинет — здесь может располагаться персонализированная лента: «Рекомендуем вам», «Просмотренные товары», «С этим товаром покупают».

Почему это важно разделять:

Персонализация — это отдельный сложный подсистемы, требующий сбора поведенческих данных, машинного обучения или как минимум эвристических алгоритмов. Смешивание её с каталогом усложняет кэширование (каждый пользователь видит уникальную выдачу), затрудняет тестирование и увеличивает нагрузку на базу данных.

Потенциальная реализация рекомендательной ленты (за рамками задания):

// Сервис рекомендаций — отдельный микросервис
type RecommendationService interface {
GetPersonalized(ctx context.Context, userID int64, limit int) ([]Product, error)
GetSimilar(ctx context.Context, productID int64, limit int) ([]Product, error)
GetFrequentlyBoughtTogether(ctx context.Context, productID int64, limit int) ([]Product, error)
}

Таким образом, архитектура закладывает возможность добавления рекомендательной системы в будущем без переработки каталога.

Вопрос 7. На каких устройствах будет работать фронтенд маркетплейса и учитываются ли старые браузеры?

Таймкод: 00:02:38

Ответ собеседника: Правильный. Фронтенд будет поддерживать все устройства, включая мобильные, планшеты и айпады. Старые браузеры типа Internet Explorer и Opera Mini не учитываются.

Правильный ответ:

Поддерживаемые устройства и платформы:

  • Десктоп — ноутбуки и стационарные компьютеры с различными разрешениями экрана (от 1280px и выше).
  • Планшеты — iPad, Android-планшеты, в том числе в портретной и ландшафтной ориентации.
  • Мобильные устройства — смартфоны на iOS и Android, включая устройства с маленькими экранами (от 320px).

Подход к адаптивности:

Применяется responsive design с использованием медиазапросов (CSS breakpoints) и гибкой сетки (flexbox/grid). Возможно применение mobile-first подхода — базовые стили пишутся для мобильных устройств, а затем расширяются для больших экранов.

Браузерная поддержка:

  • Chrome — последние 2 мажорные версии
  • Firefox — последние 2 мажорные версии
  • Safari — последние 2 мажорные версии (включая Safari на iOS)
  • Edge (Chromium-based) — последние 2 мажорные версии
  • Samsung Internet — актуальные версии

Не поддерживаются:

  • Internet Explorer (любая версия) — Microsoft прекратила поддержку в 2022 году.
  • Opera Mini — устаревший браузер с ограниченной поддержкой современных стандартов.

Это разумное решение, поскольку доля пользователей IE и Opera Mini статистически ничтожна, а поддержка этих браузеров значительно увеличивает объём работы и ограничивает использование современных технологий (CSS Grid, ES2020+ и т.д.).

Вопрос 8. Могут ли пользователи совершать покупку без авторизации?

Таймкод: 00:03:00

Ответ собеседника: Правильный. Да, предполагается, что пользователь может пройти полный флоу оплаты без авторизации.

Правильный ответ:

Да, в рамках данного маркетплейса предполагается поддержка покупки без авторизации (guest checkout). Пользователь может пройти полный путь от выбора товара до оплаты, не создавая учётную запись.

Преимущества guest checkout:

  • Снижение барьера входа — пользователю не нужно заполнять форму регистрации, подтверждать email, придумывать пароль. Это повышает конверсию.
  • Скорость покупки — меньше шагов до оплаты ниже вероятность отказа от заказа.
  • Сбор контактов — при оформлении можно предложить ввести email для уведомлений о заказе и одновременно предложить создать аккаунт постфактум.

Архитектурные последствия:

  • Корзина для неавторизованных пользователей хранится на клиенте (localStorage, cookies) или привязана к временной сессии на сервере.
  • Заказ создаётся без привязки к user_id, но с указанием контактных данных (email, телефон, адрес доставки).
  • Избранное для неавторизованных пользователей доступно только в рамках сессии/на клиенте и теряется при очистке данных браузера.
type CreateOrderRequest struct {
// UserID опционален для guest checkout
UserID *int64 `json:"user_id,omitempty"`
Email string `json:"email" validate:"required,email"`
Phone string `json:"phone" validate:"required"`
Address Address `json:"address" validate:"required"`
Items []OrderItem `json:"items" validate:"required,min=1"`
}

type OrderItem struct {
ProductID int64 `json:"product_id"`
Quantity int `json:"quantity" validate:"min=1"`
}

Важно: после успешного оформления заказа пользователю предлагается создать аккаунт на основе указанного email, чтобы в будущем видеть историю заказов и синхронизировать избранное.

Вопрос 9. Какие требования по нагрузке и скорости загрузки страницы?

Таймкод: 00:03:18

Ответ собеседника: Правильный. Система должна выдерживать до 100 000 одновременных пользователей. Страница каталога должна загружаться менее чем за 2 секунды при стабильном интернет-соединении.

Правильный ответ:

Требования к нагрузке:

  • До 100 000 одновременных пользователей (concurrent users) — это пользователи, которые активно взаимодействуют с системой в один и тот же момент времени (просматривают каталог, совершают запросы к API).
  • При этом общее количество зарегистрированных пользователей может быть значительно выше (миллионы).

Требования к производительности:

  • Страница каталога должна загружаться менее чем за 2 секунды при стабильном интернет-соединении (условно — 100 Мбит/с).
  • Под временем загрузки понимается Time to Interactive (TTI) — момент, когда страница полностью отрисована и готова к взаимодействию.

Что это означает для архитектуры:

1. API должен отвечать быстро. Целевое время ответа API каталога — не более 200-300 мс (p99), чтобы оставить время на рендеринг на клиенте, загрузку статики и сетевую латентность.

2. Кэширование обязательно. Без агрессивного кэширования база данных не выдержит 100K одновременных пользователей:

  • CDN — для статики (изображения, CSS, JS).
  • Redis/Memcached — для кэширования результатов запросов каталога, списков товаров, фильтров.
  • Application-level cache — in-memory кэш в приложении для горячих данных.

3. Горизонтальное масштабирование. API-сервисы должны быть stateless и масштабироваться горизонтально за балансировщиком.

4. Оптимизация запросов. Индексы в базе данных, денормализация для частых чтений, пагинация.

Пример расчёта нагрузки:

При 100K одновременных пользователях и среднем времени сессии 5 минут, если каждый пользователь делает 1 запрос в 30 секунд:

100 000 / 30 ≈ 3 333 RPS (запросов в секунду)

С учётом пиковых нагрузок (x3-5) система должна выдерживать 10 000 — 15 000 RPS на каталоге.

Вопрос 10. Какие важные особенности системы были упомянуты, которые не были затронуты в начальных вопросах?

Таймкод: 00:04:11

Ответ собеседника: Правильный. Важным требованием является SEO: продукты должны индексироваться и высоко ранжироваться в поисковых системах.

Правильный ответ:

Ключевое дополнительное требование — SEO-оптимизация (Search Engine Optimization). Товарные страницы и категории маркетплейса должны быть правильно индексированы поисковыми системами (Google, Яндекс) и занимать высокие позиции в поисковой выдаче.

Почему это критически важно для маркетплейса:

Органический поисковый трафик — один из основных каналов привлечения покупателей. Если карточки товаров не ранжируются в поиске, маркетплейс теряет значительную долю потенциальных клиентов.

Архитектурные следствия для бэкенда:

1. Server-Side Rendering (SSR) или Static Generation (SSG). Поисковые боты должны получать полностью отрендеренный HTML с контентом товара. Чисто клиентский SPA (React/Vue без SSR) не подходит для страниц, критичных к SEO.

2. Семантическая разметка (Structured Data / Schema.org). На страницах товаров необходимо внедрять JSON-LD разметку:

{
"@context": "https://schema.org",
"@type": "Product",
"name": "Смартфон Galaxy S24",
"image": "https://cdn.example.com/images/galaxy-s24.jpg",
"description": "Флагманский смартфон с камерой 200 Мп",
"brand": { "@type": "Brand", "name": "Samsung" },
"offers": {
"@type": "Offer",
"price": "89990.00",
"priceCurrency": "RUB",
"availability": "https://schema.org/InStock"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.7",
"reviewCount": "1253"
}
}

3. Корректные HTTP-статусы и мета-теги. API должно предоставлять данные для формирования <title>, <meta description>, <meta canonical>, Open Graph тегов.

4. Sitemap.xml и robots.txt. Генерация карты сайта со всеми товарными страницами для ускорения индексации.

5. Производительность как фактор ранжирования. Core Web Vitals (LCP, FID, CLS) напрямую влияют на позиции в поиске. Требование загрузки < 2 секунд связано и с SEO.

6. ЧПУ (человекопонятные URL). Вместо /product/12345/catalog/smartfony/samsung-galaxy-s24. Бэкенд должен поддерживать слаги и маршрутизацию по ним.

Вопрос 11. Каковы требования к геолокации и интернационализации?

Таймкод: 00:04:28

Ответ собеседника: Правильный. Рассматривается только один локальный рынок, интернационализация в рамках задачи обсуждаться не будет.

Правильный ответ:

В рамках данного задания проектируется система для одного локального рынка. Это означает:

Что исключается из скоупа:

  • Мультиязычность — весь интерфейс и контент на одном языке.
  • Мультивалютность — все цены в одной валюте.
  • Геотаргетинг — не требуется определять местоположение пользователя для показа разных цен, наличия или способов доставки.
  • Различия в налогообложении — единая логика расчёта стоимости.
  • Регуляторные различия — один набор правил для всех пользователей.

Что это даёт для архитектуры:

Упрощение проектирования — не нужно закладывать:

  • Таблицы переводов для названий и описаний товаров.
  • Механизм конвертации валют в реальном времени.
  • Гео-распределённую инфраструктуру (CDN с edge-локациями, шардирование по регионам).
  • Локальные каталоги с разным набором товаров.

При этом стоит заложить потенциал расширения:

Даже при фокусе на одном рынке, архитектура должна позволять в будущем добавить поддержку нескольких регионов без переписывания ядра системы. Например, хранить цены в минимальных единицах (копейках/центах) с явным указанием валюты:

type ProductPrice struct {
ProductID int64 `json:"product_id"`
Amount int64 `json:"amount"` // в копейках
Currency string `json:"currency"` // "RUB"
}

А в структуре товара использовать слаги вместо жёстко зашитых URL:

type Product struct {
ID int64 `json:"id"`
Slug string `json:"slug"` // "samsung-galaxy-s24"
// ...
}

Вопрос 12. Поддерживается ли доступность (accessibility) для пользователей с ограниченными возможностями?

Таймкод: 00:04:50

Ответ собеседника: Правильный. Да, должна быть обеспечена высокая доступность сервиса для людей с ограниченными возможностями, и этот аспект будет обсуждаться отдельно.

Правильный ответ:

Да, в рамках проекта упоминается требование accessibility (a11y) — обеспечение доступности сервиса для пользователей с ограниченными возможностями. При этом данный аспект выносится в отдельное обсуждение и не является частью текущего технического проектирования.

Что включает accessibility в контексте веб-приложения:

  • Screen reader compatibility — корректная семантика HTML, ARIA-атрибуты, альтернативный текст для изображений.
  • Keyboard navigation — полная навигация по сайту с помощью клавиатуры (Tab, Enter, Escape).
  • Контрастность текста — соответствие стандарту WCAG 2.1 AA (минимум 4.5:1 для обычного текста).
  • Масштабирование — корректная работа при увеличении шрифта до 200%.
  • Формы — подписи к полям, понятные сообщения об ошибках, фокус на невалидных полях.

Связь с бэкендом:

Хотя accessibility — преимущественно фронтенд-задача, бэкенд должен поддерживать её:

  • Предоставлять данные для alt-текстов изображений товаров.
  • Поддерживать структурированные данные для формирования семантически корректного HTML.
  • API должен возврачать ошибки валидации в структурированном виде, чтобы фронтенд мог корректно ассоциировать их с полями формы для screen readers.
{
"error": "validation_failed",
"fields": {
"email": "Некорректный формат email",
"phone": "Поле обязательно для заполнения"
}
}

Стандарты:

Основной ориентир — WCAG 2.1 (Web Content Accessibility Guidelines) уровня AA. В некоторых юрисдикциях (ЕС, США) соблюдение accessibility является юридическим требованием.

Вопрос 13. Как выглядит UI/UX дизайн каталога и карточки товара?

Таймкод: 00:05:44

Ответ собеседника: Правильный. В каталоге есть строка поиска, сортировка, два фильтра в виде тоглов. Карточка товара содержит большое фото, метку скидки, кнопку лайка, цену со скидкой, кнопку «добавить в корзину». При добавлении в корзину появляется счётчик с кнопками +/-. Также отображены состояния лайкнутого и нелайкнутого товара.

Правильный ответ:

Страница каталога — элементы интерфейса:

  • Строка поиска — полнотекстовый поиск по товарам с автоподсказками.
  • Сортировка — выпадающий список или переключатель (по цене, популярности, рейтингу, новизне).
  • Фильтры-тоглы (toggle switches) — два переключателя:
    • «Только в наличии» (in stock)
    • «Со скидкой» (has discount)
  • Сетка товаров — карточки товаров в виде сетки (grid layout), адаптивной под размер экрана.
  • Пагинация — постраничная навигация или бесконечная прокрутка.

Карточка товара (Product Card) в каталоге:

  • Фото товара — основное изображение, занимающее значительную часть карточки.
  • Метка скидки — бейдж с процентом скидки (например, «-20%»), если sale_price < base_price.
  • Кнопка лайка (избранное) — иконка сердечка, переключающаяся между двумя состояниями:
    • Неактивное (контур сердечка) — товар не в избранном.
    • Активное (заливка сердечка) — товар добавлен в избранное.
  • Цена — отображается sale_price (если есть скидка) или base_price. Старая цена может быть зачёркнута.
  • Кнопка «Добавить в корзину» — основное действие на карточке.

После добавления в корзину:

Кнопка «Добавить в корзину» заменяется на счётчик количества с кнопками «−» и «+», отображающий текущее количество единиц данного товара в корзине. Это позволяет быстро изменить количество без перехода в корзину.

Состояния товара (для фронтенда):

type ProductCardResponse struct {
ID int64 `json:"id"`
Name string `json:"name"`
ImageURL string `json:"image_url"`
BasePrice float64 `json:"base_price"`
SalePrice *float64 `json:"sale_price,omitempty"`
DiscountPercent *int `json:"discount_percent,omitempty"`
InStock bool `json:"in_stock"`
IsFavorite bool `json:"is_favorite"` // лайкнут ли текущим пользователем
InCartQuantity int `json:"in_cart_quantity"` // количество в корзине (0 если нет)
}

Важные UX-детали:

  • Все интерактивные элементы должны иметь визуальный отклик (hover, active, focus states).
  • Изображения должны загружаться лениво (lazy loading) для оптимизации.
  • Карточки должны быть кликабельны целиком (или хотя бы по названию/фото) для перехода на детальную страницу.

Вопрос 14. Как организовано дерево компонентов фронтенда и как компоненты взаимодействуют друг с другом?

Таймкод: 00:07:01

Ответ собеседника: Правильный. Корневой компонент — App. Основной компонент — Catalog, который содержит ProductList и Sidebar. ProductList включает итерацию по компонентам Product с нужными полями и пагинацию. Sidebar содержит инпут поиска, сортировку и фильтры. Cart отвечает за взаимодействие с хранилищем и взаимодействует с клиентским стейтом. Cart и CartItem получают Price и Total Price с сервера.

Правильный ответ:

Дерево компонентов:

App
├── Catalog
│ ├── Sidebar
│ │ ├── SearchInput — строка поиска
│ │ ├── SortDropdown — выбор сортировки
│ │ ├── FilterInStock — тогл «Только в наличии»
│ │ └── FilterDiscount — тогл «Со скидкой»
│ └── ProductList
│ ├── ProductCard[] — список карточек товаров
│ │ └── ProductCard — отдельная карточка
│ └── Pagination — пагинация
├── ProductDetail — страница товара (отдельный роут)
│ ├── ProductGallery — галерея изображений
│ ├── ProductInfo — название, описание, цена
│ ├── AddToCartButton — кнопка/счётчик добавления в корзину
│ └── FavoriteToggle — кнопка избранного
└── CartDrawer / CartPage — корзина
├── CartItem[] — список товаров в корзине
└── CartSummary — итого

Взаимодействие компонентов:

1. Sidebar → Catalog (подъём состояния)

Sidebar содержит элементы управления (поиск, сортировка, фильтры). При изменении любого параметра событие поднимается вверх к Catalog, который формирует новый запрос к API и передаёт результат в ProductList.

2. Catalog → ProductCard (props)

Catalog получает список товаров от API и передаёт каждый товар как props в ProductCard. Карточка не делает собственные запросы — она получает все необходимые данные от родителя.

3. ProductCard → Cart (глобальное хранилище)

При нажатии «Добавить в корзину» карточка обновляет глобальное хранилище (Redux, Zustand, Vuex или React Context). Компонент Cart подписан на это хранилище и перерисовывается автоматически.

4. Cart ↔ Сервер / Локальное хранилище

Cart синхронизирует состояние корзины с сервером (для авторизованных пользователей) или с localStorage/cookies (для гостей), чтобы сохранять корзину между сессиями.

Ключевое архитектурное решение:

Цены (Price, Total Price) вычисляются на сервере, а не на клиенте. Это критически важно, потому что:

  • Логика ценообразования может быть сложной (скидки, промокоды, динамическое ценообразование).
  • Клиент не может быть доверенным источником цен — пользователь может подменить данные.
  • Будущие изменения (купоны, акции, персональные скидки) не потребуют обновления клиентского кода.
// Структура ответа API для корзины
type CartResponse struct {
Items []CartItem `json:"items"`
TotalPrice float64 `json:"total_price"` // вычислено на сервере
Currency string `json:"currency"`
}

type CartItem struct {
ProductID int64 `json:"product_id"`
Name string `json:"name"`
ImageURL string `json:"image_url"`
UnitPrice float64 `json:"unit_price"` // цена за единицу (от сервера)
Quantity int `json:"quantity"`
TotalPrice float64 `json:"total_price"` // unit_price * quantity (от сервера)
}

Вопрос 15. Какие API-точки (эндпоинты) необходимы для взаимодействия с бэкендом и какие данные они принимают/возвращают?

Таймкод: 00:09:58

Ответ собеседника: Правильный. GET /products — список продуктов с query-параметрами пагинации, сортировки, фильтрации и поиска. GET /products/:productId — детальная информация о товаре. POST /cart — добавление товара в корзину. PUT /cart — изменение количества. DELETE /cart — удаление товара.

Правильный ответ:

Полный набор API-эндпоинтов:

1. GET /api/v1/products — Список товаров каталога

Query-параметры:

ПараметрТипОписание
pageintНомер страницы (по умолчанию 1)
page_sizeintКоличество элементов на странице (по умолчанию 20, максимум 100)
sortstringПоле сортировки: price_asc, price_desc, popularity, rating, newest
searchstringПоисковый запрос
in_stockboolФильтр «только в наличии»
has_discountboolФильтр «со скидкой»

Ответ:

{
"products": [
{
"id": 123,
"name": "Смартфон Galaxy S24",
"description": "Флагманский смартфон...",
"image_url": "https://cdn.example.com/galaxy-s24.jpg",
"base_price": 89990.00,
"sale_price": 71992.00,
"discount_percent": 20,
"in_stock": true,
"is_favorite": false,
"in_cart_quantity": 0,
"rating": 4.7,
"brand": "Samsung"
}
],
"pagination": {
"current_page": 1,
"page_size": 20,
"total_items": 1543,
"total_pages": 77
}
}

2. GET /api/v1/products/:productId — Детальная информация о товаре

Ответ расширен дополнительными полями: галерея изображений, характеристики, описание, отзывы.

3. POST /api/v1/cart/items — Добавить товар в корзину

// Request
{
"product_id": 123,
"quantity": 2
}

// Response
{
"items": [...],
"total_price": 143984.00,
"items_count": 2,
"currency": "RUB"
}

4. PUT /api/v1/cart/items/:productId — Изменить количество

// Request
{
"quantity": 3
}

// Response — аналогичен POST /cart/items

5. DELETE /api/v1/cart/items/:productId — Удалить из корзины

// Response — аналогичен POST /cart/items

6. GET /api/v1/cart — Получить текущую корзину

// Response
{
"items": [
{
"product_id": 123,
"name": "Смартфон Galaxy S24",
"image_url": "https://cdn.example.com/galaxy-s24.jpg",
"unit_price": 71992.00,
"quantity": 2,
"total_price": 143984.00
}
],
"total_price": 143984.00,
"items_count": 2,
"currency": "RUB"
}

7. POST /api/v1/favorites — Добавить в избранное

// Request
{
"product_id": 123
}

8. DELETE /api/v1/favorites/:productId — Удалить из избранного

9. GET /api/v1/favorites — Список избранных товаров

Формат ответа аналогичен GET /products, но содержит только товары, добавленные текущим пользователем в избранное.

Пример обработки в Go:

// GET /api/v1/products
func (h *Handler) ListProducts(c *gin.Context) {
var req ListProductsRequest
if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()})
return
}

products, total, err := h.productService.List(c.Request.Context(), req)
if err != nil {
c.JSON(http.StatusInternalServerError, ErrorResponse{Error: "internal error"})
return
}

c.JSON(http.StatusOK, ListProductsResponse{
Products: products,
Pagination: Pagination{
CurrentPage: req.Page,
PageSize: req.PageSize,
TotalItems: total,
TotalPages: (total + req.PageSize - 1) / req.PageSize,
},
})
}

Вопрос 16. Где и как хранить данные корзины между сессиями пользователя?

Таймкод: 00:11:37

Ответ собеседника: Правильный. Корзину нужно хранить на бэкенде, а не в localStorage или cookies, потому что пользователь может использовать разные устройства и должен видеть свои товары на всех платформах.

Правильный ответ:

Корзина должна храниться на сервере (бэкенде), а не исключительно на клиенте. Основная причина — кросс-устройственная синхронизация: пользователь может добавить товар в корзину с мобильного телефона, а оформить заказ с десктопа. При хранении только в localStorage или cookies данные привязаны к конкретному браузеру на конкретном устройстве.

Подход к хранению:

Для авторизованных пользователей:

Корзина хранится в базе данных, привязана к user_id. При каждом действии (добавление, удаление, изменение количества) обновляется запись в БД.

CREATE TABLE cart_items (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
product_id BIGINT NOT NULL REFERENCES products(id),
quantity INT NOT NULL CHECK (quantity > 0),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE (user_id, product_id)
);

CREATE INDEX idx_cart_user ON cart_items(user_id);

Для неавторизованных пользователей (guest):

Корзина привязана к сессии через session_id, который хранится в cookie. При авторизации/регистрации происходит мержинг гостевой корзины с пользовательской.

CREATE TABLE guest_cart_items (
id BIGSERIAL PRIMARY KEY,
session_id VARCHAR(128) NOT NULL,
product_id BIGINT NOT NULL REFERENCES products(id),
quantity INT NOT NULL CHECK (quantity > 0),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
UNIQUE (session_id, product_id)
);

CREATE INDEX idx_guest_cart_session ON guest_cart_items(session_id);

Логика мержинга при авторизации:

func (s *CartService) MergeGuestCart(ctx context.Context, sessionID string, userID int64) error {
guestItems, err := s.repo.GetGuestCartItems(ctx, sessionID)
if err != nil {
return err
}

return s.repo.Transaction(ctx, func(tx *sql.Tx) error {
for _, item := range guestItems {
existing, err := s.repo.GetCartItemTx(tx, userID, item.ProductID)
if err != nil {
return err
}
if existing != nil {
// Товар уже в корзине — берём максимальное количество
newQty := max(existing.Quantity, item.Quantity)
if err := s.repo.UpdateQuantityTx(tx, existing.ID, newQty); err != nil {
return err
}
} else {
if err := s.repo.AddToUserCartTx(tx, userID, item.ProductID, item.Quantity); err != nil {
return err
}
}
}
// Очищаем гостевую корзину
return s.repo.ClearGuestCartTx(tx, sessionID)
})
}

Оптимизация чтения:

Для ускорения чтения корзины (один из самых частых запросов) используется кэширование в Redis:

  • Ключ: cart:{user_id} или cart:session:{session_id}
  • TTL: 1 час, обновляется при каждом изменении корзины.
  • При записи в БД — инвалидация кэша.

Важно: цены в корзине не фиксируются при добавлении. При каждом запросе корзины цены пересчитываются на основе актуальных данных товаров. Это защищает от ситуации, когда пользователь добавил товар по одной цене, а через час цена изменилась.

Вопрос 17. Какой стек технологий и инструментов выбрать для фронтенд-приложения и почему?

Таймкод: 00:13:47

Ответ собеседника: Правильный. Предлагается использовать Prettier, ESLint, Stylelint для единого стиля кода. Husky для прекоммита. Commitlint для стандартизации коммитов. TypeScript для статической типизации. Vitest и Testing Library для тестов. При выборе фреймворка важно учитывать популярность, документацию, активность сообщества.

Правильный ответ:

Основной стек фронтенда:

Фреймворк: React + Next.js (или Nuxt.js для Vue)

Выбор Next.js обусловлен требованием SEO — Next.js поддерживает Server-Side Rendering (SSR) и Static Site Generation (SSG) из коробки, что критически важно для индексируемости товарных страниц.

Язык: TypeScript

Статическая типизация обязательна для проекта такого масштаба:

  • Раннее обнаружение ошибок на этапе компиляции.
  • Улучшенный developer experience — автодополнение, навигация по коду.
  • Безопасность при рефакторинге — компилятор подскажет, где сломался контракт.
  • Документирование контрактов между компонентами через типы.

Качество кода:

ИнструментНазначение
ESLintСтатический анализ JS/TS-кода, поиск потенциальных ошибок и стилистических проблем
PrettierАвтоматическое форматирование кода — единый стиль без ручных правок
StylelintЛинтинг CSS/SCSS — контроль стилей
HuskyGit-хуки — запуск проверок перед коммитом
lint-stagedЗапуск линтеров только для изменённых файлов (ускоряет pre-commit)
CommitlintВалидация формата коммит-сообщений (Conventional Commits)

Тестирование:

ИнструментНазначение
VitestБыстрый раннер тестов (совместим с Vite, значительно быстрее Jest)
React Testing LibraryЮнит- и интеграционные тесты компонентов
PlaywrightE2E-тесты критических пользовательских сценариев

Управление состоянием:

  • React Query (TanStack Query) — для серверного состояния (кэширование, рефетчинг, оптимистичные обновления).
  • Zustand или Context API — для клиентского состояния (UI-стейт, корзина).

Важность критериев выбора инструментов:

При выборе любого инструмента или библиотеки следует оценивать:

  • Активность сообщества — частота коммитов, количество contributors, скорость закрытия issues.
  • Качество документации — наличие руководств, примеров, API-спецификации.
  • Зрелость — мажорная версия, обратная совместимость.
  • Экосистема — количество сопутствующих библиотек и интеграций.
  • Долгосрочная поддержка — есть ли компания-спонсор или foundation за проектом.

Это защищает от ситуации, когда библиотека перестаёт поддерживаться через полгода после начала разработки.

Пример конфигурации Husky + lint-staged:

// package.json
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{css,scss}": ["stylelint --fix"]
}
}

Вопрос 18. Почему выбраны React или Vue в качестве фреймворков и каковы их основные преимущества и недостатки?

Таймкод: 00:18:36

Ответ собеседника: Правильный. React имеет большую экосистему, поддержку Meta, лёгкий найм, но требует самостоятельной сборки стека. Vue — полноценный фреймворк с низким порогом входа, но меньше комьюнити и готовых enterprise-решений.

Правильный ответ:

Выбор между React и Vue определяется несколькими ключевыми факторами, оба фреймворка являются зрелыми и подходят для проекта уровня маркетплейса.

React:

Преимущества:

  • Крупнейшая экосистема — максимальное количество библиотек, компонентов, решений и готовых интеграций.
  • Поддержка Meta (Facebook) — гарантия долгосрочной поддержки и развития.
  • Рынок труда — наибольшее количество разработчиков и вакансий на СНГ и мировом рынке, что упрощает найм и онбординг.
  • Гибкость — не навязывает архитектурных решений, команда сама выбирает оптимальный стек под свои задачи.
  • React Native — возможность переиспользования знаний и части логики для мобильной разработки.

Недостатки:

  • Библиотека, а не фреймворк — необходимо самостоятельно выбирать и интегрировать решения для роутинга (React Router), управления состоянием (Redux, Zustand, MobX), серверного рендеринга (Next.js), запросов к API (React Query, SWR).
  • Более высокий порог входа — необходимо понимать множество концепций (hooks, closures, memo, контексты).
  • Частые изменения экосистемы — быстрое устаревание библиотек и подходов.

Vue:

Преимущества:

  • Полноценный фреймворк — официальные решения для роутинга (Vue Router), стейт-менеджмента (Pinia/Vuex), SSR (Nuxt.js) — всё единообразно задокументировано и интегрировано.
  • Продвинутая реактивность — автоматическое отслеживание зависимостей, минимальный boilerplate.
  • Низкий порог входа — декларативные шаблоны, интуитивный синтаксис, проще для новых разработчиков.
  • Composition API — аналогичен React Hooks, но с более явной реактивной моделью.
  • Документация — считается одной из лучших среди фронтенд-фреймворков.

Недостатки:

  • Меньшее комьюнити — меньше готовых решений, библиотек и специалистов на рынке труда по сравнению с React.
  • Меньше enterprise-кейсов — меньше примеров использования в крупных проектах.
  • Разделение на Vue 2 и Vue 3 — миграция между версиями нетривиальна.

Рекомендация для данного проекта:

Для маркетплейса с требованиями SEO, высокой нагрузки и необходимостью найма команды предпочтительнее React + Next.js благодаря большей экосистеме, наличию SSR из коробки через Next.js и доступности кадров на рынке.

Вопрос 19. Какие дополнительные библиотеки и инструменты выбрать для связки с React и почему?

Таймкод: 00:21:21

Ответ собеседника: Правильный. React Router для навигации, Zustand для стейт-менеджмента, TanStack Query для серверного стейта, Tailwind для стилей, Next.js для SSR и SEO.

Правильный ответ:

Для формирования полноценного стека на базе React необходимо дополнить его следующими библиотеками:

Next.js — фреймворк поверх React

Next.js выбран как основа проекта, поскольку он обеспечивает:

  • Server-Side Rendering (SSR) — критически важно для SEO, страницы товаров и категорий должны отдаваться как готовый HTML.
  • Static Site Generation (SSG) — для страниц, которые редко меняются.
  • File-based routing — автоматическая генерация маршрутов на основе структуры файлов.
  • API Routes — возможность размещения бэкенд-эндпоинтов в том же проекте (для BFF-паттерна).
  • Image Optimization — встроенная оптимизация изображений (lazy loading, WebP, responsive).

TanStack Query (React Query) — управление серверным состоянием

Заменяет ручное управление loading/error-состояниями и кэшированием:

  • Автоматическое кэширование — данные кешируются по ключу запроса.
  • Stale-While-Revalidate — показывает кэшированные данные мгновенно, обновляет в фоне.
  • Оптимистичные обновления — мгновенный UI-отклик при добавлении в корзину/избранное.
  • Автоматический retry — повторные попытки при сбоях сети.
  • Prefetching — предзагрузка данных при наведении на ссылку.
// Пример использования TanStack Query для каталога
const { data, isLoading, error } = useQuery({
queryKey: ['products', { page, sort, search, inStock, hasDiscount }],
queryFn: () => fetchProducts({ page, sort, search, inStock, hasDiscount }),
staleTime: 30_000, // 30 секунд данные считаются свежими
});

Zustand — клиентское состояние

Минималистичный стейт-менеджер для UI-состояния (открытие/закрытие сайдбара, состояние корзины на клиенте):

  • Минимальный boilerplate — не требует providers, actions, reducers.
  • TypeScript-friendly — отличная поддержка типов.
  • Малый размер — ~1 кб в bundle.
const useCartUIStore = create<CartUIState>((set) => ({
isOpen: false,
open: () => set({ isOpen: true }),
close: () => set({ isOpen: false }),
toggle: () => set((state) => ({ isOpen: !state.isOpen })),
}));

React Hook Form + Zod — формы и валидация

  • React Hook Form — производительная работа с формами без лишних ре-рендеров.
  • Zod — схема валидации с автоматической типизацией для TypeScript.

Tailwind CSS — стилизация

  • Utility-first подход — быстрая разработка UI без написания CSS вручную.
  • Малый bundle — PurgeCSS удаляет неиспользуемые классы в production.
  • Консистентность — дизайн-система через конфигурацию (цвета, spacing, breakpoints).
  • Responsive — встроенные брейкпоинты для адаптивной вёрстки.

Axios или Fetch с обёрткой — HTTP-клиент

Для запросов к API с перехватчиками (interceptors) для обработки ошибок, добавления токенов авторизации, логирования.

Вопрос 20. Какие альтернативы стейт-менеджерам существуют и почему выбран Zustand?

Таймкод: 00:21:47

Ответ собеседника: Правильный. Redux — много boilerplate. Redux Toolkit — меньше кода, thunks. MobX — мутации и обзёрверы, но мутабельность ухудшает предсказуемость. Zustand — простой API без провайдеров. Минус — самый новый, меньше решений для сложных систем.

Правильный ответ:

Обзор основных стейт-менеджеров для React:

Redux (классический)

Золотой стандарт индустрии, используется в крупнейших проектах мира.

  • Плюсы: предсказуемость (single source of truth), отличные devtools, огромная экосистема middleware, time-travel debugging.
  • Минусы: огромное количество boilerplate-кода (actions, action creators, reducers, store configuration), сложный порог входа, избыточность для простых задач.

Redux Toolkit (RTK)

Официальная надстройка над Redux, призванная устранить недостатки классического Redux.

  • Плюсы: значительно меньше boilerplate через createSlice, встроенные createAsyncThunk, createEntityAdapter, Immer для иммутабельных обновлений через мутации, RTK Query для серверного состояния.
  • Минусы: всё ещё требует Provider, концепция middleware может быть избыточной для небольших проектов.

MobX

Основан на парадигме реактивного программирования.

  • Плюсы: минимальный boilerplate, автоматическое отслеживание зависимостей, мутабельный стейт (писать state.count++ вместо создания нового объекта).
  • Минусы: мутабельность снижает предсказуемость и усложняет отладку, «магия» реактивности не всегда очевидна, менее популярная экосистема.

Zustand

Минималистичный стейт-менеджер от создателей Jotai.

  • Плюсы:
    • Невероятно простой API — store создаётся одной функцией.
    • Нет Provider — используется напрямую через хук.
    • Нет boilerplate — нет actions, reducers, dispatch.
    • Основан на хуках — нативно интегрирован с React.
    • Малый размер (~1 кб).
    • Поддержка TypeScript из коробки.
  • Минусы: моложе конкурентов, меньше готовых решений и паттернов для очень сложных систем, нет встроенных devtools (но есть middleware для Redux DevTools).

Почему Zustand для данного проекта:

Для маркетплейса клиентский стейт относительно прост — состояние корзины, UI-флаги (открыт/закрыт сайдбар), состояние модальных окон. Серверный состояние (список товаров, данные корзины) обрабатывается TanStack Query. Zustand идеально покрывает потребности клиентского стейта без избыточности Redux.

// Пример Zustand store для UI каталога
const useCatalogStore = create<CatalogUIState>((set) => ({
sidebarOpen: false,
viewMode: 'grid', // 'grid' | 'list'
toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
setViewMode: (mode) => set({ viewMode: mode }),
}));

Вопрос 21. Почему выбран метафреймворк (Next.js) вместо кастомного решения для SSR и какие преимущества это даёт?

Таймкод: 00:23:19

Ответ собеседника: Правильный. Метафреймворк предоставляет готовую архитектуру со встроенными оптимизациями и маршрутизацией, уменьшает boilerplate, предоставляет серверный рендеринг из коробки, улучшает SEO и пользовательский опыт.

Правильный ответ:

Почему не кастомное решение:

Настройка SSR с нуля на базе React — это сложная инженерная задача, включающая:

  • Конфигурацию webpack/Vite для серверной и клиентской сборки.
  • Реализацию серверного рендеринга (ReactDOMServer).
  • Настройку гидрации (hydration) на клиенте.
  • Оркестрацию загрузки данных на сервере перед рендером (getInitialProps или аналоги).
  • Code splitting для серверного и клиентского бандлов.
  • Настройку маршрутизатора для работы в обоих окружениях.

Это десятки и сотни часов работы, которые нужно поддерживать и обновлять.

Преимущества Next.js:

1. SSR, SSG и ISR из коробки

// Страница каталога с серверным рендерингом
export async function getServerSideProps(context) {
const { page, sort, search, inStock, hasDiscount } = context.query;
const data = await fetchProducts({ page, sort, search, inStock, hasDiscount });
return { props: { data } };
}

// Или Static Generation с ревалидацией (ISR)
export async function getStaticProps() {
const data = await fetchProducts({ page: 1 });
return {
props: { data },
revalidate: 60, // перегенерировать каждые 60 секунд
};
}

2. File-based routing

Структура файлов автоматически генерирует маршруты:

  • pages/catalog/index.tsx/catalog
  • pages/products/[id].tsx/products/:id

3. Встроенные оптимизации

  • Image Optimization — автоматический lazy loading, конвертация в WebP, responsive images.
  • Font Optimization — автоматическая оптимизация шрифтов через next/font.
  • Code Splitting — автоматическое разделение кода по страницам.
  • Prefetching — автоматическая предзагрузка страниц при появлении ссылок в viewport.

4. API Routes (BFF)

Возможность размещать серверные эндпоинты в том же проекте:

// pages/api/products.ts
export default async function handler(req, res) {
const products = await productService.list(req.query);
res.status(200).json(products);
}

Это удобно для паттерна Backend for Frontend — промежуточный слой между фронтендом и микросервисами бэкенда.

5. Middleware

Next.js Middleware выполняется на границе (edge) до рендеринга страницы — можно реализовать редиректы, A/B тестирование, гео-маршрутизацию.

6. Экосистема и поддержка

Next.js поддерживается Vercel, имеет огромное сообщество, регулярные обновления и отличную документацию. Это снижает риски по сравнению с кастомным решением, которое нужно поддерживать самостоятельно.

Итог: Next.js закрывает все ключевые требования проекта — SEO через SSR, производительность через встроенные оптимизации, и скорость разработки через готовую архитектуру — без необходимости строить и поддерживать собственное решение.

Вопрос 22. Как серверный рендеринг улучшает SEO и какие ещё преимущества он даёт?

Таймкод: 00:23:49

Ответ собеседника: Правильный. React SPA отдаёт минимальный HTML, что мешает краулерам. SSR готовит HTML на сервере. Также SSR улучшает FCP и TTI, снижает CLS, позволяет персонализацию.

Правильный ответ:

Проблема SPA для SEO:

Чистое React-приложение (SPA) при загрузке отдаёт HTML-шаблон вида:

<!DOCTYPE html>
<html>
<head><title>Marketplace</title></head>
<body>
<div id="root"></div>
<script src="bundle.js"></script>
</body>
</html>

Поисковые боты (Googlebot, YandexBot) видят пустой div — контент появляется только после выполнения JavaScript. Хотя Google научился выполнять JS, это происходит с задержкой и не гарантирует полную индексацию. Яндекс и другие поисковики могут вообще не выполнить JS.

Как SSR решает проблему:

При SSR сервер выполняет React-код, получает данные из API и генерирует полный HTML с контентом:

<!DOCTYPE html>
<html>
<head>
<title>Смартфон Galaxy S24 — купить на Marketplace</title>
<meta name="description" content="Флагманский смартфон с камерой 200 Мп. Цена от 71 990 ₽. Доставка по всей России.">
</head>
<body>
<div id="root">
<div class="product-card">
<img src="galaxy-s24.jpg" alt="Смартфон Galaxy S24">
<h1>Смартфон Galaxy S24</h1>
<span class="price">71 990 ₽</span>
<!-- полный контент страницы -->
</div>
</div>
<script src="bundle.js"></script>
</body>
</html>

Бот получает готовый HTML с семантической разметкой, мета-тегами и контентом — страница корректно индексируется.

Преимущества SSR помимо SEO:

1. First Contentful Paint (FCP)

Пользователь видит контент сразу, без ожидания загрузки и выполнения JavaScript. Для маркетплейса это критично — каждая секунда загрузки снижает конверсию.

2. Time to Interactive (TTI)

Хотя TTI при SSR может быть чуть выше из-за гидрации, воспринимаемая скорость загрузки значительно лучше — пользователь видит контент и может начать взаимодействие.

3. Cumulative Layout Shift (CLS)

Готовый HTML с правильными размерами изображений и шрифтов снижает сдвиги layout по сравнению с SPA, где контент «выезжает» после загрузки данных.

4. Персонализация на сервере

SSR позволяет формировать страницу с учётом данных пользователя до отправки клиенту — показывать избранное, персональные цены, рекомендации.

5. Social Media Preview

При шеринге ссылки в соцсетях (Open Graph, Twitter Cards) боты соцсетей читают HTML. SSR гарантирует, что превью будет содержать корректное изображение, заголовок и описание товара.

Ограничения SSR:

  • Увеличенная нагрузка на сервер — каждая страница рендерится на сервере.
  • Нельзя использовать браузерные API (window, document) в серверном коде.
  • Гидратация добавляет время до полной интерактивности.

Для маркетплейса преимущества SSR перевешивают ограничения, особенно с учётом требований к SEO.

Вопрос 23. Какие существуют стратегии рендеринга и каковы их плюсы и минусы?

Таймкод: 00:26:37

Ответ собеседния: Правильный. CSR — простая архитектура, но слабое SEO. SSR — улучшает FCP и SEO, но нагружает сервер. SSG — быстрый отклик, хорошо масштабируется. ISR — пересборка по таймеру. Streaming SSR — отдача по частям. Также гибридные техники.

Правильный ответ:

1. Client-Side Rendering (CSR)

Браузер получает пустой HTML-шаблон, загружает JavaScript-бандл, который рендерит интерфейс на клиенте.

  • Плюсы: простая архитектура, минимальная нагрузка на сервер, богатая интерактивность после загрузки.
  • Минусы: большой объём JS-бандла, медленная первоначальная отрисовка (белый экран), плохое SEO (пустой HTML для ботов), зависимость от производительности устройства пользователя.

2. Server-Side Rendering (SSR)

Сервер для каждого запроса рендерит полный HTML с данными и отдаёт клиенту.

  • Плюсы: отличное SEO, быстрый FCP, контент доступен до загрузки JS, персонализация на сервере.
  • Минусы: высокая нагрузка на сервер (каждый запрос = рендер), более медленный TTFB (Time to First Byte), стоимость инфраструктуры выше.

3. Static Site Generation (SSG)

Страницы генерируются в виде статических HTML-файлов на этапе сборки (build time).

  • Плюсы: максимально быстрый отклик (отдаётся из CDN), минимальная нагрузка на сервер, отличное SEO, высокая надёжность (нет серверного кода).
  • Минусы: долгая сборка при большом количестве страниц (миллионы товаров), контент может устареть между пересборками, не подходит для динамических данных.

4. Incremental Static Regeneration (ISR)

Гибрид SSG и SSR: страница генерируется статически при сборке, но может быть пересобрана на сервере после истечения TTL при следующем запросе.

export async function getStaticProps() {
const products = await fetchProducts();
return {
props: { products },
revalidate: 60, // пересобрать не чаще раза в 60 секунд
};
}
  • Плюсы: скорость SSG + актуальность данных, не требует полной пересборки всего сайта.
  • Минусы: первая запись после TTL — медленная (нужно пересобрать), stale данные до ревалидации.

5. Streaming SSR

Сервер отправляет HTML клиенту по частям (chunks) по мере готовности, не дожидаясь полного рендеринга.

  • Плюсы: быстрый TTFB, пользователь видит контент по мере поступления, критические части страницы загружаются первыми.
  • Минусы: сложная реализация, требует React 18+ и поддержки на уровне фреймворка.

6. Гибридный подход (рекомендуемый)

Для маркетплейса оптимально комбинировать стратегии:

СтраницаСтратегияОбоснование
ГлавнаяISRКонтент обновляется часто, но не в реальном времени
Категории каталогаISRФильтры и сортировка — динамические, но базовый контент можно кэшировать
Карточка товараSSR или ISRКритична для SEO, цена и наличие могут меняться
КорзинаCSRПерсональные данные, не нужна SEO
Личный кабинетCSRПриватные данные, не индексируется

Next.js позволяет выбирать стратегию рендеринга на уровне каждой страницы, что даёт максимальную гибкость.

Вопрос 24. Как распределить страницы маркетплейса по различным стратегиям рендеринга и на что опираться при выборе?

Таймкод: 00:28:27

Ответ собеседника: Правильный. Учитываются: нужна ли индексация, частота изменения данных, персонализация, возможность кэширования. Карточки товара — SSR для SEO. Гибрид для рекомендаций (верх — SSR, низ — CSR). Поиск — CSR. Корзина и личный кабинет — CSR.

Правильный ответ:

Критерии выбора стратегии рендеринга:

1. Требуется ли SEO-индексация?

Если страница должна ранжироваться в поиске — нужен SSR или SSG/ISR. Если страница приватная (корзина, профиль) — CSR.

2. Как часто меняются данные?

Статичный контент (FAQ, о компании) — SSG. Относительно стабильный (категории) — ISR. Динамичный в реальном времени (наличие на складе, цены) — SSR.

3. Нужна ли персонализация?

Если контент одинаков для всех — SSG/ISR. Если зависит от пользователя (история заказов, избранное) — CSR или SSR с серверной логикой.

4. Насколько хорошо страница кэшируется?

Если можно кэшировать для всех пользователей — ISR/SSG. Если уникальна для каждого запроса — SSR без кэширования.

Распределение страниц по стратегиям:

СтраницаСтратегияОбоснование
ГлавнаяISR (revalidate: 60s)Баннеры, акции обновляются часто, но не мгновенно. Критична для SEO.
Каталог / КатегорииISR или SSRСписок товаров можно кэшировать, но фильтры динамичны. Важна SEO-индексация категорий.
Карточка товара (верх)SSRНазвание, фото, цена, описание — критичны для SEO. Цена и наличие актуальны.
Карточка товара (рекомендации)CSR«Похожие товары» загружаются отдельным запросом — не блокирует основной контент.
Результаты поискаCSRПараметры поиска уникальны для каждого запроса, кэширование неэффективно. SEO не критично (search pages обычно не индексируются).
КорзинаCSRПерсональные данные, не нужна SEO. Требуется интерактивность (изменение количества).
Личный кабинетCSRПриватные данные, не индексируется.
Оформление заказаCSRПерсональные данные, много форм и интерактивности.

Пример гибридного подхода для карточки товара:

// pages/products/[id].tsx — SSR для основного контента
export async function getServerSideProps({ params }) {
const product = await fetchProduct(params.id);
return { props: { product } };
}

// Компонент страницы
export default function ProductPage({ product }) {
return (
<div>
{/* SSR — основной контент */}
<ProductInfo product={product} />
<AddToCartButton productId={product.id} />

{/* CSR — рекомендации загружаются отдельно */}
<Suspense fallback={<RecommendationsSkeleton />}>
<Recommendations productId={product.id} />
</Suspense>
</div>
);
}

// Клиентский компонент рекомендаций
function Recommendations({ productId }) {
const { data } = useQuery({
queryKey: ['recommendations', productId],
queryFn: () => fetchRecommendations(productId),
});
return <ProductGrid products={data} />;
}

Такой подход обеспечивает быструю загрузку основного контента через SSR (SEO + FCP) и интерактивность рекомендаций через CSR (не блокирует рендер).

Вопрос 25. Какие дополнительные факторы положительно влияют на SEO, помимо серверного рендеринга?

Таймкод: 00:30:25

Ответ собеседника: Правильный. Корректные метаданные, Open Graph теги, Schema.org разметка, sitemap XML, семантический HTML, производительность страницы, предварительная генерация для популярных запросов, человекочитаемые URL.

Правильный ответ:

1. Уникальные метаданные для каждой страницы

Каждая страница товара и категории должна иметь уникальные <title> и <meta description>, содержащие ключевые слова:

<title>Смартфон Samsung Galaxy S24 256GB — купить за 71 990 ₽ | Marketplace</title>
<meta name="description" content="Смартфон Samsung Galaxy S24 256GB фанси чёрный. Камера 200 Мп, процессор Snapdragon 8 Gen 3. Доставка по России от 1 дня. Рейтинг 4.7 из 5.">

Без уникальных метаданных Google может понизить страницу в выдаче или показать нерелевантный сниппет.

2. Open Graph теги (социальный SEO)

<meta property="og:title" content="Samsung Galaxy S24 — 71 990 ₽">
<meta property="og:description" content="Флагманский смартфон с камерой 200 Мп">
<meta property="og:image" content="https://cdn.example.com/galaxy-s24-og.jpg">
<meta property="og:type" content="product">

OG-теги обеспечивают красивые превью при шеринге в Telegram, WhatsApp, Facebook, VK — это косвенно влияет на трафик и поведенческие факторы.

3. Структурированные данные (Schema.org / JSON-LD)

Разметка позволяет поисковикам показывать расширенные сниппеты:

{
"@context": "https://schema.org",
"@type": "Product",
"name": "Samsung Galaxy S24",
"image": "https://cdn.example.com/galaxy-s24.jpg",
"offers": {
"@type": "Offer",
"price": "71990.00",
"priceCurrency": "RUB",
"availability": "https://schema.org/InStock"
},
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.7",
"reviewCount": "1253"
}
}

Расширенные сниппет с ценой, рейтингом и наличием повышают CTR в поиске.

4. Sitemap.xml и robots.txt

Автоматически генерируемая карта сайта со всеми товарными страницами:

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://marketplace.ru/products/samsung-galaxy-s24</loc>
<lastmod>2024-01-15</lastmod>
<changefreq>daily</changefreq>
<priority>0.8</priority>
</url>
</urlset>

5. Семантический HTML

Использование семантических тегов вместо <div>:

<header>...</header>
<nav aria-label="Каталог">...</nav>
<main>
<article>
<h1>Samsung Galaxy S24</h1>
<section aria-label="Характеристики">...</section>
</article>
</main>
<footer>...</footer>

Это помогает поисковикам понимать структуру страницы и улучшает accessibility.

6. Производительность (Core Web Vitals)

Google использует метрики производительности как фактор ранжирования:

  • LCP (Largest Contentful Paint) < 2.5с — скорость загрузки основного контента.
  • FID (First Input Delay) < 100мс — скорость отклика на первое взаимодействие.
  • CLS (Cumulative Layout Shift) < 0.1 — стабильность layout.

7. Человекочитаемые URL (Clean URLs)

❌ /product?id=12345
✅ /catalog/smartfony/samsung-galaxy-s24-256gb

ЧПУ содержат ключевые слова, понятны пользователю и улучшают CTR в поиске.

8. Канонические URL и пагинация

Для предотвращения дублей:

<link rel="canonical" href="https://marketplace.ru/catalog/smartfony">

Для пагинации:

<link rel="prev" href="https://marketplace.ru/catalog/smartfony?page=1">
<link rel="next" href="https://marketplace.ru/catalog/smartfony?page=3">

9. Предварительная генерация для популярных запросов

Используя getStaticPaths в Next.js, можно предгенерировать страницы для топ-1000 популярных товаров при сборке, а остальные — генерировать по запросу (fallback: 'blocking'):

export async function getStaticPaths() {
const topProducts = await getTopProducts(1000);
return {
paths: topProducts.map(p => ({ params: { id: p.slug } })),
fallback: 'blocking',
};
}

Вопрос 26. Какие архитектуры фронтенд-приложений существуют и почему выбрана Feature-Sliced Design (FSD)?

Таймкод: 00:32:32

Ответ собеседника: Правильный. Существуют классическая, модульная, Atomic Design и FSD. Выбрана FSD как специально созданная для фронтенда. Плюсы: бизнес-ориентированность, однонаправленный поток, иерархия слоёв, изоляция через public API. Минусы: высокий порог входа, избыточность для маленьких проектов.

Правильный ответ:

Обзор архитектурных подходов:

1. Классическая (Framework Default)

Стандартная структура, генерируемая Create React App или Next.js: components/, pages/, styles/, utils/.

  • Плюсы: минимум решений, быстрый старт.
  • Минусы: при росте проекта папки раздуваются, нет чётких правил организации, легко создать циклические зависимости.

2. Модульная (Domain-Driven)

Группировка по бизнес-доменам: products/, cart/, auth/, profile/. Каждый модуль содержит свои компоненты, хуки, типы.

  • Плюсы: логическая группировка, легче навигация.
  • Минусы: нет строгих правил междумодульного взаимодействия, возможно дублирование логики.

3. Atomic Design

Компоненты классифицируются по уровню сложности: Atoms → Molecules → Organisms → Templates → Pages.

  • Плюсы: единая терминология, переиспользуемость компонентов.
  • Минусы: границы между уровнями размыты (к чему отнести сложную форму?), не решает вопрос бизнес-логики.

4. Feature-Sliced Design (FSD)

Архитектурная методология, разработанная специально для фронтенд-приложений.

Слои FSD (снизу вверх):

СлойНазначениеПример
sharedПереиспользуемый код без привязки к бизнес-логикеUI-kit, API-клиент, утилити, константы
entitiesБизнес-сущностиProduct, Cart, User — модель, API, UI-превью
featuresПользовательские сценарииAddToCart, ToggleFavorite, SearchProducts
widgetsСамостоятельные блоки интерфейсаProductCard, CartDrawer, CatalogFilters
pagesСтраницы приложенияCatalogPage, ProductPage, CartPage
appИнициализация приложенияПровайдеры, роутинг, глобальные стили

Ключевые принципы FSD:

  • Однонаправленные импорты — каждый слой может импортировать только из слоёв ниже. features не может импортировать из widgets, только из entities и shared.
  • Public API — каждый слайс (модуль) экспортирует только то, что нужно другим модулям, через index.ts.
  • Изоляция фич — фича «Добавить в корзину» не знает о фиче «Переключить избранное», они общаются через entities.

Почему FSD для маркетплейса:

  • Масштабируемость — при росте команды до 10+ разработчиков каждый может работать над своими фичами без конфликтов.
  • Бизнес-ориентированность — структура проекта отражает бизнес-логику, а не технические детали.
  • Тестируемость — изолированные фичи легко тестировать по отдельности.
  • Постепенное внедрение — можно начать с одного модуля и расширять.

Минусы FSD:

  • Высокий порог входа для новых разработчиков.
  • Избыточная структура для небольших проектов (лендинг, простой сайт).
  • Требует строгого соблюдения правил — иначе деградирует в хаос.
  • Проблемы с кросс-фичами (например, корзина + избранное = «переместить в корзину из избранного») требуют тщательного проектирования.

Вопрос 27. Как распределить компоненты маркетплейса по слоям Feature-Sliced Design?

Таймкод: 00:34:08

Ответ собеседника: Правильный. App — инициализация и провайдеры. Pages — страница каталога. Shared — UI-kit. Entities — Product и Cart. Features — избранное, корзина, фильтрация, пагинация, поиск. Widgets — header, footer, каталог, сайдбар.

Правильный ответ:

Подробное распределение по слоям FSD:

app/ — Инициализация приложения

app/
├── index.tsx — точка входа, рендер корневого компонента
├── providers/ — провайдеры контекстов
│ ├── router.tsx — настройка React Router
│ ├── query.tsx — QueryClient Provider (TanStack Query)
│ └── store.tsx — инициализация Zustand (если нужен провайдер)
└── styles/ — глобальные стили, CSS-переменные

shared/ — Переиспользуемый код

shared/
├── ui/ — UI-компоненты (кнопки, инпуты, модалки, лоадеры)
│ ├── Button/
│ ├── Input/
│ ├── Checkbox/
│ ├── Select/
│ ├── Skeleton/
│ └── Modal/
├── api/ — HTTP-клиент (Axios instance с interceptors)
├── lib/ — утилити-функции (formatPrice, debounce, classNames)
├── config/ — константы приложения (API_URL, PAGE_SIZE)
└── types/ — общие типы (Pagination, SortOrder)

entities/ — Бизнес-сущности

entities/
├── product/
│ ├── api/ — запросы к API (getProducts, getProduct)
│ ├── model/ — типы и интерфейсы (Product, ProductCard)
│ ├── ui/ — превью-компонент (ProductCard для списка)
│ └── index.ts — public API
├── cart/
│ ├── api/ — запросы (getCart, addItem, updateItem, removeItem)
│ ├── model/ — типы (Cart, CartItem), Zustand store
│ ├── ui/ — мини-превью (CartBadge с количеством)
│ └── index.ts
└── favorite/
├── api/ — toggleFavorite, getFavorites
├── model/ — типы, локальный стор
└── index.ts

features/ — Пользовательские сценарии

features/
├── add-to-cart/ — добавление товара в корзину
│ ├── ui/ — AddToCartButton с переключением на счётчик
│ ├── model/ — логика оптимистичного обновления
│ └── index.ts
├── toggle-favorite/ — добавление/удаление из избранного
│ ├── ui/ — FavoriteToggle (сердечко)
│ └── model/ — мутация через TanStack Query
├── catalog-search/ — поиск по каталогу
│ ├── ui/ — SearchInput с debounce
│ └── model/ — управление поисковым запросом
├── catalog-filters/ — фильтры каталога
│ ├── ui/ — FilterInStock, FilterDiscount (тоглы)
│ └── model/ — управление состоянием фильтров
└── catalog-sort/ — сортировка
├── ui/ — SortDropdown
└── model/ — управление параметром сортировки

widgets/ — Самостоятельные блоки интерфейса

widgets/
├── header/ — шапка сайта (лого, поиск, иконки корзины/избранного)
├── footer/ — подвал сайта
├── catalog-list/ — сетка товаров с пагинацией
│ ├── ui/ — CatalogGrid + Pagination
│ └── model/ — объединение фильтров, сортировки, поиска
├── catalog-sidebar/ — боковая панель с фильтрами и сортировкой
└── product-card/ — карточка товара (композиция из entities/product + features)

pages/ — Страницы

pages/
├── catalog/ — страница каталога
│ ├── ui/ — CatalogPage (собирает sidebar + catalog-list)
│ └── index.ts — экспорт + getServerSideProps
├── product/ — страница товара
│ ├── ui/ — ProductPage
│ └── index.ts
└── cart/ — страница корзины
├── ui/ — CartPage
└── index.ts

Ключевой пример — карточка товара как композиция:

// widgets/product-card/ui/ProductCard.tsx
import { ProductCard } from '@/entities/product' // UI сущности
import { AddToCartButton } from '@/entities/cart' // UI корзины
import { FavoriteToggle } from '@/features/toggle-favorite' // Фича

export function ProductCardWidget({ product }: { product: Product }) {
return (
<div className="product-card">
<img src={product.imageUrl} alt={product.name} />
<FavoriteToggle productId={product.id} /> {/* feature */}
<h3>{product.name}</h3>
<span>{formatPrice(product.salePrice)}</span> {/* shared/lib */}
<AddToCartButton productId={product.id} /> {/* entity cart */}
</div>
);
}

Каждый компонент находится на своём слое и может использовать только слои ниже — это обеспечивает предсказуемость и предотвращает циклические зависимости.

Вопрос 28. Какие способы улучшения доступности (accessibility) сайта существуют и как их применить на практике?

Таймкод: 00:35:24

Ответ собеседника: Правильный. Семантические теги, alt у изображений, навигация с клавиатуры, скринридеры, ARIA-атрибуты (aria-labelledby, aria-live, aria-describedby, role, aria-current). Основная интерактивность на нативных элементах, ARIA — как дополнение.

Правильный ответ:

Принцип: «No ARIA is better than bad ARIA»

Первое правило accessibility — используйте нативные HTML-элементы везде, где это возможно. Нативные элементы имеют встроенную доступность бесплатно.

1. Семантический HTML

Вместо <div onClick={...}> используйте <button>. Вместо <div className="nav"><nav>.

<!-- Плохо -->
<div className="header" onClick={openMenu}>...</div>

<!-- Хорошо -->
<header>
<nav aria-label="Основная навигация">
<button aria-expanded={isOpen} aria-controls="menu">Меню</button>
</nav>
</header>

2. Изображения — alt-текст

<!-- Информативные изображения -->
<img src="galaxy-s24.jpg" alt="Смартфон Samsung Galaxy S24, черный корпус">

<!-- Декоративные изображения -->
<img src="decoration.svg" alt="" role="presentation">

Для товаров alt-текст должен описывать товар, а не содержать ключевые слова для SEO.

3. Навигация с клавиатуры

  • Все интерактивные элементы должны быть фокусируемыми (tabindex, :focus-visible).
  • Порядок фокуса должен быть логичным (соответствует визуальному порядку).
  • Кастомные компоненты (dropdown, modal) должны управляться клавишами (Escape для закрытия, Enter/Space для активации).

4. ARIA-атрибуты — дополнение к семантике

АтрибутПрименение в маркетплейсе
aria-labelКнопка с иконкой без текста: <button aria-label="Добавить в корзину">
aria-live="polite"Уведомление «Товар добавлен в корзину» — скринридер озвучит без прерывания
aria-live="assertive"Критические ошибки: «Недостаточно товара на складе»
aria-atomic="true"Озвучивать весь блок целиком при изменении
aria-current="page"Текущая страница в пагинации
aria-describedbyДополнительное описание для сложных элементов
aria-expandedСостояние раскрывающихся элементов (фильтры, аккордеоны)

5. Пример — уведомление о добавлении в корзину:

function AddToCartButton({ productId }: { productId: number }) {
const [added, setAdded] = useState(false);

return (
<>
<button
onClick={() => { addToCart(productId); setAdded(true); }}
aria-label={`Добавить ${productName} в корзину`}
>
<CartIcon /> В корзину
</button>
{added && (
<div role="status" aria-live="polite" aria-atomic="true">
{productName} добавлен в корзину
</div>
)}
</>
);
}

6. Контрастность и размер текста

  • Минимальное соотношение контраста 4.5:1 для текста (WCAG AA).
  • Текст должен масштабироваться до 200% без потери функциональности.
  • Размер кликабельных элементов минимум 44×44px (для мобильных).

7. Тестирование доступности

  • Автоматизированные инструменты: axe-core, Lighthouse (вкладка Accessibility), eslint-plugin-jsx-a11y.
  • Ручное тестирование: навигация только клавиатурой (Tab, Enter, Escape, стрелки).
  • Screen readers: NVDA (Windows, бесплатный), VoiceOver (macOS/iOS), TalkBack (Android).

8. Бэкенд-поддержка accessibility:

API должен возвращать структурированные данные для формирования корректных alt-текстов и ARIA-атрибутов:

{
"id": 123,
"name": "Samsung Galaxy S24",
"image": {
"url": "https://cdn.example.com/galaxy-s24.jpg",
"alt": "Смартфон Samsung Galaxy S24, черный матовый корпус, вид спереди"
}
}
type ImageDTO struct {
URL string `json:"url"`
Alt string `json:"alt"`
}

type ProductCardDTO struct {
ID int64 `json:"id"`
Name string `json:"name"`
Image ImageDTO `json:"image"`
}

Вопрос 29. Какие оптимизации можно внедрить для фронтенда маркетплейса?

Таймкод: 00:37:58

Ответ собеседника: Правильный. Оптимизация изображений (WebP, picture, адаптивные, lazy loading). Виртуализация для Infinite Scroll. Optimistic Update для избранного. Оптимизация поиска (debounce, throttle, отмена запросов, кэширование, префетчинг). Шрифты, code splitting, lazy loading, мемоизация, tree shaking, prefetching.

Правильный ответ:

Оптимизация изображений:

Изображения — основной потребитель трафика в e-commerce. Одна страница каталога может содержать 20-50 картинок товаров.

<!-- Адаптивные изображения с форматом WebP -->
<picture>
<source
srcset="/images/galaxy-s24-400w.webp 400w,
/images/galaxy-s24-800w.webp 800w"
type="image/webp"
sizes="(max-width: 768px) 400px, 800px"
/>
<source
srcset="/images/galaxy-s24-400w.jpg 400w,
/images/galaxy-s24-800w.jpg 800w"
type="image/jpeg"
sizes="(max-width: 768px) 400px, 800px"
/>
<img
src="/images/galaxy-s24-800w.jpg"
alt="Смартфон Samsung Galaxy S24"
loading="lazy"
decoding="async"
width="800"
height="600"
/>
</picture>

Ключевые техники:

  • WebP/AVIF — на 30-50% меньше размер по сравнению с JPEG.
  • Адаптивные изображения (srcset, sizes) — мобильные устройства загружают маленькие версии.
  • Lazy loading (loading="lazy") — изображение загружается только при приближении к viewport.
  • Явные width/height — предотвращает CLS (сдвиг layout).

Виртуализация списков:

При отображении большого количества товаров (особенно с бесконечной прокруткой) рендеринг всех DOM-элементов замедляет страницу.

import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualProductList({ products }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: products.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 350, // примерная высота карточки
overscan: 5, // рендерить на 5 элементов больше с каждой стороны
});

return (
<div ref={parentRef} style={{ height: '100vh', overflow: 'auto' }}>
<div style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map((item) => (
<div key={item.key} style={{ position: 'absolute', top: item.start }}>
<ProductCard product={products[item.index]} />
</div>
))}
</div>
</div>
);
}

Видны 20 карточек, но в DOM находятся только ~30 (с буфером), а не 1000+.

Optimistic Updates:

Мгновенный отклик UI без ожидания ответа сервера:

const toggleFavorite = useMutation({
mutationFn: ({ productId, isFavorite }) =>
isFavorite ? removeFavorite(productId) : addFavorite(productId),
onMutate: async ({ productId, isFavorite }) => {
// Отменяем текущие запросы
await queryClient.cancelQueries({ queryKey: ['favorites'] });
// Сохраняем предыдущее состояние для отката
const previous = queryClient.getQueryData(['favorites']);
// Оптимистично обновляем
queryClient.setQueryData(['favorites'], (old) =>
isFavorite ? old.filter(id => id !== productId) : [...old, productId]
);
return { previous };
},
onError: (err, vars, context) => {
// Откат при ошибке
queryClient.setQueryData(['favorites'], context.previous);
},
});

Оптимизация поиска:

function useSearch() {
const [query, setQuery] = useState('');

// Debounce — ждём 300мс после последнего ввода
const debouncedQuery = useDebounce(query, 300);

const { data } = useQuery({
queryKey: ['search', debouncedQuery],
queryFn: () => searchProducts(debouncedQuery),
enabled: debouncedQuery.length >= 2, // не искать при < 2 символах
staleTime: 60_000, // кэш на 60 секунд
});

return { query, setQuery, results: data };
}

Дополнительные оптимизации:

  • Code splittingReact.lazy() + Suspense для ленивой загрузки компонентов по роутам.
  • Prefetching — предзагрузка данных при наведении на ссылку (onMouseEnter).
  • МемоизацияReact.memo, useMemo, useCallback для предотвращения лишних ре-рендеров.
  • Tree shaking — удаление неиспользуемого кода при сборке (автоматически в Vite/Webpack).
  • Оптимизация шрифтовfont-display: swap, предзагрузка через <link rel="preload">, subsetting (только нужные символы).
  • Service Worker — кэширование статики и API-ответов для офлайн-работы (PWA).

Вопрос 30. На какие метрики опираются при оценке производительности фронтенда?

Таймкод: 00:44:08

Ответ собеседника: Правильный. Core Web Vitals: LCP — время до крупного элемента (CDN, кэш, SSR). INP — время до интерактивности (code splitting, web workers). CLS — стабильность layout (размеры изображений, aspect ratio). Дополнительно: FCP и TTFB.

Правильный ответ:

Core Web Vitals — метрики Google для ранжирования:

1. LCP (Largest Contentful Paint)

Время от начала загрузки до отображения самого большого видимого элемента (обычно это hero-изображение, заголовок или блок товара).

  • Цель: < 2.5с (хорошо), 2.5-4с (требует улучшения), > 4с (плохо).
  • Как улучшить:
    • CDN для статики и изображений.
    • SSR/SSG — контент приходит в HTML.
    • Предзагрузка критических ресурсов (<link rel="preload">).
    • Оптимизация изображений (WebP, lazy loading, адаптивные размеры).
    • Кэширование на сервере (Redis, HTTP-заголовки Cache-Control).

2. INP (Interaction to Next Paint)

Задержка между действием пользователя (клик, нажатие клавиши, тач) и визуальным откликом страницы. Заменил FID в марте 2024 года.

  • Цель: < 200мс (хорошо), 200-500мс (требует улучшения), > 500мс (плохо).
  • Как улучшить:
    • Уменьшение объёма JavaScript в основном потоке.
    • Code splitting — загрузка только необходимого кода.
    • Отложенная загрузка некритичных скриптов (defer, async).
    • Web Workers для тяжёлых вычислений вне main thread.
    • Debounce/throttle для обработчиков событий.
    • Мемоизация компонентов для предотвращения лишних ре-рендеров.

3. CLS (Cumulative Layout Shift)

Суммарный сдвиг визуальных элементов во время загрузки. Пользователь видит, как кнопки «прыгают», текст сдвигается.

  • Цель: < 0.1 (хорошо), 0.1-0.25 (требует улучшения), > 0.25 (плохо).
  • Как улучшить:
    • Явные width и height у изображений и видео.
    • CSS aspect-ratio для резервирования места.
    • Фиксированное место для рекламных блоков и динамического контента.
    • Предзагрузка шрифтов с font-display: swap.
    • Избегать вставки контента над существующим (кроме уведомлений по действию пользователя).

Дополнительные метрики:

МетрикаОписаниеЦель
TTFB (Time to First Byte)Время до первого байта от сервера< 800мс
FCP (First Contentful Paint)Время до отображения первого элемента< 1.8с
TBT (Total Blocking Time)Суммарное время блокировки main thread< 200мс
TTI (Time to Interactive)Время до полной интерактивности< 3.8с

Инструменты измерения:

  • Lighthouse — встроен в Chrome DevTools, даёт оценку и рекомендации.
  • PageSpeed Insights — онлайн-инструмент Google с полевыми и лабораторными данными.
  • Web Vitals Library — npm-пакет для сбора метрик в продакшене.
  • Chrome User Experience Report (CrUX) — реальные данные от пользователей Chrome.

Мониторинг в продакшене:

import { onCLS, onINP, onLCP } from 'web-vitals';

function reportMetric(metric) {
// Отправка метрик в аналитику
analytics.send('web_vital', {
name: metric.name,
value: metric.value,
rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
});
}

onCLS(reportMetric);
onINP(reportMetric);
onLCP(reportMetric);

Вопрос 31. Как организовать мониторинг и наблюдаемость фронтенда?

Таймкод: 00:46:37

Ответ собеседника: Правильный. Мониторинг нужен, так как проблемы могут проявляться на других устройствах/браузерах. Prometheus + Grafana для метрик (время ответа API, ошибки, время рендера, Core Web Vitals). Sentry для сбора ошибок с контекстом. Алертинг при выходе метрик за пороговые значения.

Правильный ответ:

Зачем нужен мониторинг:

Без мониторинга фронтенд — «чёрный ящик». Тесты проходят на ограниченном наборе окружений, но в продакшене пользователи используют сотни комбинаций устройств, браузеров, версий ОС, разрешений экрана и сетевых условий. Проблемы, невидимые в тестовой среде, могут затрагивать значительную долю аудитории.

Стек мониторинга:

1. Sentry — Error Tracking

Автоматический сбор ошибок JavaScript с полным контекстом:

  • Stack trace ошибки.
  • Информация об окружении (браузер, ОС, версия, разрешение экрана).
  • Действия пользователя до ошибки (breadcrumbs).
  • Связь с релизами — какие ошибки появились после конкретного деплоя.
import * as Sentry from '@sentry/react';

Sentry.init({
dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0',
release: 'marketplace@1.2.3', // привязка к версии
environment: 'production',
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration({
maskAllText: false,
blockAllMedia: false,
}),
],
tracesSampleRate: 0.1, // 10% запросов для performance tracing
replaysSessionSampleRate: 0.01, // 1% сессий для replay
replaysOnErrorSampleRate: 1.0, // 100% сессий с ошибками
});

2. Prometheus + Grafana — метрики

Сбор, хранение и визуализация числовых метрик:

  • Время ответа API-эндпоинтов (p50, p95, p99).
  • Количество ошибок 4xx/5xx от бэкенда.
  • Core Web Vitals в реальном времени.
  • Crash Rate — процент сессий с ошибками.

3. Web Vitals — производительность в продакшене

Реальные данные от пользователей (RUM — Real User Monitoring), в отличие от лабораторных тестов Lighthouse.

Алертинг:

Настройка уведомлений при выходе метрик за допустимые пороги:

МетрикаПорогДействие
Crash Rate> 1%Критический алерт — немедленный откат
Crash Rate> 0.5%Предупреждение — расследование
LCP p95> 4сПредупреждение — проверить CDN/сервер
API Error Rate> 5%Критический — проблемы на бэкенде
INP p95> 500мсПредупреждение — проверить JS bundle

Продуктовые метрики:

Помимо технических, отслеживайте бизнес-метрики:

  • Конверсия из просмотра каталога в добавление в корзину.
  • Время от поиска до клика по товару.
  • Процент пользователей, использующих фильтры.
  • Среднее количество просмотренных карточек до покупки.

Связь релизов и метрик:

Каждый деплой должен быть привязан к версии и времени, чтобы можно было быстро определить, после какого релиза ухудшились метрики:

// Бэкенд также должен экспонировать метрики
// Prometheus формат: /metrics
http.Handle("/metrics", promhttp.Handler())