Dobro.rf: В 13 раз больше запросов в секунду


- Роль
- Frontend Team Lead
- Команда
- 15 человек: 5 frontend, 4 backend, 2 дизайнера, 2 QA, PM и DevOps
- Срок
- С марта 2026
- Статус
- В разработке, запуск запланирован на I квартал 2027
Что сделал я
- КодКодовая база — 37 страниц, из них 20 в кабинетах, и 110 переиспользуемых компонентов, покрытых тестами. Сам написал UI-кит и поля форм, кабинеты НКО и доноров, страницы НКО и сбора, главную, каталог, шапку и подвал, раздел модерации.
- КомандаСтавил задачи и приоритеты четырём фронтенд-разработчикам, ревьюил и сливал PR. Написал регламенты и онбординг-документ, чтобы новые разработчики быстрее входили в проект и писали код в одном стиле.
- Архитектура и стекВыбрал стек и заложил архитектуру фронтенда. React задал заказчик, а Next.js поверх него отдаёт страницы сборов и НКО готовыми с сервера: их находят поисковики, а донор видит сбор сразу, без ожидания загрузки. TypeScript в строгом режиме, без any и as, ловит ошибки до релиза, а это важно там, где через сайт проходят деньги. Компоненты разложены по слоям ui → form → base → страницы, Server Components по умолчанию, на каждый компонент есть тесты.
- ПроизводительностьСайт выдерживает в 13 раз больше запросов в секунду, а сервер готовит страницу за 69 мс вместо 919. Для благотворительной платформы это значит, что сайт не тормозит, когда на него разом приходит много доноров, например после рассылки или в большую акцию. Всю оптимизацию провёл сам, от замеров до отчёта.
Контекст
Бизнес
Добро.рф (dobro.ru) — платформа прозрачной благотворительности, генеральный партнёр — Альфа-Банк. Доноры делают разовые и регулярные пожертвования, собирают корзину из нескольких сборов и смотрят историю. НКО и частные организаторы проходят онбординг, загружают документы, создают сборы и следят за статистикой. Модераторы проверяют сборы.
Ситуация
У заказчика уже была платформа, но за основу её не брали. Заказали новую с нуля: с новыми техническими требованиями и полностью новым дизайном. Поэтому архитектуру, дизайн-систему и интеграции строили заново, без наследия старого кода. Разработка идёт с марта 2026 по ТЗ заказчика.
Стек тоже задал заказчик: React. До проекта во фронтенд-команде на React писал только я, поэтому команде нужно было быстро освоить новый стек. При этом интеграций много: цифровой рубль, оплата в модальном окне прямо на сайте, Альфа-Банк, многоступенчатый онбординг НКО.
Процесс, архитектура и решения
Путь пожертвования
Донор может поддержать сразу несколько сборов одной оплатой: деньги распределяются автоматически, вся история остаётся в кабинете.
- 01КаталогФильтры по городу, направлению помощи, реестру СО НКО и приёму цифровых рублей. В разработке: каталог с кешем запросов, справочник фильтров вынесен из HTML, чтобы страница грузилась быстрее
- 02СборПрогресс, «осталось дней», отчёты и журнал транзакций. В разработке: страница в ISR, она отдаётся готовой и обновляется сама
- 03КорзинаНесколько сборов, одна оплата, распределение автоматически. В разработке: состояние корзины в Zustand, одна форма оплаты на все сборы
- 04ОплатаКарта в модальном окне на сайте, СБП или цифровой рубль. В разработке: способы оплаты приходят с бэкенда, данные карты вводятся в защищённые поля платёжного шлюза
Онбординг НКО
Организация попадает на платформу через мастер из шести шагов. Замечания модератора показываются ошибками полей и не дают отправить анкету, пока их не исправят.
- 01ОрганизацияВход через Госуслуги, реквизиты с подсказками. В разработке: подсказки DaData через серверный роут, ключ не попадает в браузер
- 02АнкетаСведения об организации. В разработке: формы на react-hook-form и zod, замечания модератора выводятся ошибками полей
- 03СчётВход через Alfa ID и открытие счёта. В разработке: подключение Alfa ID и возврат в мастер на тот же шаг
- 04ДокументыЗагрузка PDF. В разработке: загрузка файлов с проверкой формата
- 05ПодписаниеПодписание документов. В разработке: шаг недоступен, пока не загружены все документы
- 06ЗавершениеИтог проверки, в том числе отказ. В разработке: отдельный экран для каждого итога проверки
Архитектура
- STOREFRONTПубличная витрина
Главная, каталог сборов, страница сбора, организации, документы. Главная в статике, сборы и НКО в ISR, каталог и организации — динамика с кешем запросов. Публичные страницы отдаются готовыми с сервера: их находят поисковики, а донор быстро открывает сбор по ссылке из соцсетей или рассылки.
- ACCOUNTSКабинеты
Кабинеты доноров и НКО, раздел модерации. Данные грузятся на клиенте: SWR и Zustand, формы на react-hook-form и zod. Кабинеты личные и поисковикам не нужны, поэтому данные грузятся в браузере и не нагружают сервер, а проверка форм не даёт отправить анкету с ошибкой.
- SERVERСерверные роуты Next.js
Прокси к API, подсказки DaData с ключом только на сервере, метрики приложения. Ключи платных сервисов не попадают в браузер, поэтому их нельзя украсть и потратить лимит, а метрики показывают рост нагрузки раньше, чем его заметят пользователи.
app/api/backendapp/api/dadataapp/metrics
- APIREST бэкенда
Типы ответов генерируются из OpenAPI, поэтому фронтенд узнаёт об изменении контракта на typecheck. Расхождения с бэкендом ловятся до релиза, а не на странице оплаты у донора.
Дизайн-система
Макеты рисовали два дизайнера команды в Figma. По ним фронтенд собрал свою дизайн-систему: токены темы, CSS-компоненты кнопок, карточек и полей, 110 переиспользуемых компонентов. Каждый переиспользуемый компонент принимается через Storybook.
Интеграции
- Банковские карты · защищённые поля шлюза
- СБП
- Цифровой рубль
- Alfa ID
- Госуслуги
- DaData
- Яндекс Метрика
Интеграциями на фронтенде руководил я: по каждой выбрал подход и инструменты, поставил задачи группе и принимал результат на ревью.
Оплата — одна из самых сложных частей платформы: три способа, и у каждого свои правила. Картой донор платит прямо на сайте, в модальном окне, без перехода на страницу банка. Поля для номера карты встраивает платёжный шлюз, поэтому сайт данные карты не видит и не хранит. Через СБП донор подтверждает платёж в приложении своего банка, а сайт дожидается ответа и показывает результат. Цифровой рубль — новая форма денег от Центробанка, и им можно оплатить только разовое пожертвование, а корзину и ежемесячные пожертвования — картой или через СБП. Какие способы доступны, решает бэкенд для каждого сбора и вида пожертвования, и сайт показывает донору только подходящие.
Безопасность данных
Ключ DaData живёт только на сервере, запросы идут через серверный роут. HTTP-клиент не отправляет cookie в кешируемых запросах, поэтому персональный ответ не может попасть в общий кеш. Для частных сборов есть согласие на публикацию персональных данных.
Качество и релизы
Vitest и React Testing Library: 48 файлов, около 450 тест-кейсов, базовые тесты на каждый компонент. Отдельно покрыты швы производительности: cookie и кеш в HTTP-клиенте, кеширование публичных ручек, клиентская загрузка справочников. E2E на Playwright пишет другой разработчик: корзина и пожертвование, авторизация, онбординг НКО.
- husky линт и юнит-тесты на пуше
- pull request ревью и CI
- develop dev-стенд
- preprod prod
Проверки на пуше настроил я, GitHub Actions, Docker и деплой — DevOps команды.
Работаем недельными спринтами с демо в конце каждого. Задачи в Jira, ветка на задачу, PR с ревью. Для новых разработчиков есть онбординг-документ: слои, нейминг, паттерны, формы, тесты, Storybook. Для AI-агентов — правила и скиллы проекта: ревью ветки, тесты, токены темы.
Сложности
20 rps на любой странице
- Проблема
- Корневой layout на каждый запрос ждал два ответа API, до секунды: проверку авторизации и данные шапки, из которых использовалось одно поле. Из-за чтения cookie все 39 маршрутов были динамическими, кеша не было нигде.
- Решение
- Замерил оба варианта и перенёс авторизацию в клиент: 182 rps против 15,5 у серверного с кешем шапки. Публичные ручки пометил на кеширование с тегами, главную перевёл в статику, страницы сбора и НКО — в ISR, справочник фильтров на 130 КБ вынес из HTML, кабинеты перевёл на клиентскую загрузку, 109 PNG — в webp.
- Итог
- 17 маршрутов: 340 → 4552 rps суммарно, медиана ответа 919 → 69 мс. Статических маршрутов стало 17 и ещё 2 в ISR, HTML каталога похудел с 254 до 169 КБ, картинки в сборке — с 13 до 1,4 МБ.
Ускорение, которое оказалось пустой страницей
- Проблема
- После перевода страницы сбора в ISR замер показал рост в 15 раз. На деле сервер отдавал пустую оболочку: useSearchParams() ронял страницу в клиентский рендер, в HTML осталось 659 символов текста вместо 2398. Ни typecheck, ни 444 теста этого не поймали.
- Решение
- Нашёл это сам на ревью своей ветки. Чтение параметра суммы вынесли в эффект, а в регламент добавили проверку маркеров клиентского рендера и объёма видимого текста после перевода маршрута в статику.
- Итог
- Страница осталась в ISR и держит 196 rps уже с полным контентом.
Метрики и результаты
- ×13больше запросов в секунду: 340 → 4552 rps
- 69 мсмедиана ответа вместо 919 мс
- 100Lighthouse в Accessibility, Best Practices и SEO, Performance 90+
- 37страниц, из них 20 в кабинетах
Нагрузку мерил autocannon на prod-сборке, 20 соединений. Lighthouse — Unlighthouse по всем страницам сайта. Бизнес-метрик пока нет: платформа ещё не вышла в релиз.
Нужен большой проект на Next.js или команда, которая быстро освоит новый стек?
Заложу архитектуру и регламенты, выстрою работу фронтенд-команды и разгоню сайт под нагрузку.