yt-wrapped.md 22.04.2026

YT-wrapped. Аналитика YouTube-каналов

Аналитика YouTube-каналов: поиск, разбор последних 50 видео, вовлечённость, частота загрузок и рейтинг роликов.

Задача

Дашборд, который по имени канала собирает цифры, которых нет на самом YouTube. Без OAuth и аккаунтов: пользователь вводит запрос, получает список каналов, выбирает один — и видит средние просмотры, лайки, комментарии, вовлечённость в процентах, частоту загрузок, суммарную длину контента, лучший и худший ролик, а также сортируемую таблицу видео.

Как делал

Поиск сделан в два запроса, а не в один. search.list возвращает только id канала плюс обрывки сниппета — статистики там нет. Поэтому сначала забираем до десяти channelId, а затем одним запросом channels.list с id=id1,id2,... догружаем счётчики, баннер и темы. Квота тратится один раз вместо десяти, а задержка не удваивается — поиск всё ещё один round-trip.

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

Картинки с YouTube прогоняются через локальные роуты: домены аватаров и превью подменяются на свои префиксы, потому что при прямом обращении к CDN у аватаров слетает referrer-политика и часть каналов отдаёт заглушку вместо аватарки.

Ключ можно привести с собой: если поле не заполнено, роут берёт ключ из YOUTUBE_API_KEY в окружении. Это снимает вопрос «у меня не работает», когда у человека нет своего ключа, но не заставляет никого его заводить. Кэш при этом отключён явно — revalidate: 0, потому что счётчики подписчиков устаревают за минуты.

Даты в таблице не форматируются библиотекой, а считаются вручную с делением на сутки: «сегодня», «вчера», «Nd ago», затем недели, месяцы и годы. В длинной ленте точные даты превращаются в кашу, а такая шкала остаётся читаемой на любом горизонте.

Длительность приходит из API строкой в формате ISO 8601 (PT1H2M3S) и разбирается регуляркой на часы, минуты и секунды — дальше из неё считается и суммарная длительность всего канала, и колонка в таблице. Сортировка по любой колонке переиспользует один компаратор: направление вынесено в множитель -1, а разница типов для дат сводится к тем же числам через getTime().

Итог

Next.js 14 на App Router, TypeScript в строгом режиме, Tailwind и семь shadcn-компонентов на @base-ui/react. Логика аналитики вынесена в lib/youtube.ts и живёт в server-компоненте с "use server" — ключ не покидает сервер, а клиент получает только готовые числа и сортируемую таблицу.

// похожие проекты

Ещё работы

// проект 2026
Скриншот главной страницы лендинга DRIVE24

DRIVE24. Лендинг сервиса каршеринга

Лендинг сервиса каршеринга на WordPress + Elementor: тарифы, автопарк, инструкция, приложение, отзывы и форма заявки.

// проект 2026
Скриншот главной страницы сайта ayupov

Ayupov. Сайт-портфолио

Сайт-портфолио на своей теме WordPress с нуля: живой терминал, скрамбл-заголовок, CSS одним ответом.

// проект 2026
Скриншот главной страницы лединга Salt and Cedar

Salt and cedar. Лендинг курорта у моря

Лендинг курорта у моря на WordPress и бесплатном Elementor: услуги, преимущества, бронирование и форма заявки.

// проект 2026
Скриншот главной страницы лединга AROMA

Aroma. Лендинг кофейни

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

// проект 2026
Скриншот страницы сайта chetaika с попапом

Chetaika. Книжный поисковик

Книжный поисковик поверх Google Books API: один HTML-файл, два serverless-прокси и чистый vanilla JS без зависимостей.

// проект 2026
Скриншот главной страницы лендинга ИГРАЙ-КА

«Играй-ка». Лендинг магазина игрушек

Одностраничный лендинг магазина детских игрушек: витрина, категории, отзывы и форма заявки.

> все проекты

// contact.php

Ну что, работаем?

Опишите задачу — разберусь и предложу решение.