Как мы переработали архитектуру сайта для снижения нагрузки на фронтенд

Материал выпуска

Когда сайт начинает расти, фронтенд часто первым принимает на себя удар: больше страниц, больше логики, больше интерактива, больше медленных запросов и лишнего JavaScript. Мы столкнулись именно с этим сценарием и решили не «допиливать» симптомы, а пересобрать архитектуру так, чтобы интерфейс стал легче, предсказуемее и дешевле в поддержке.

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

Почему фронтенд вообще начал перегружаться

Проблема редко появляется из-за одной причины. Обычно это накопленный эффект. На практике я вижу это постоянно: команда добавляет фичу за фичей, а фундамент остаётся прежним — и в какой-то момент сайт начинает напоминать перегруженный грузовик, который ещё едет, но каждый новый подъём даётся всё тяжелее.

Вот типичный набор факторов, который складывается годами:

  • слишком много логики на клиенте — вычисления, трансформации данных, валидации, которые можно сделать раньше;
  • крупные бандлы и повторяющиеся зависимости — одни и те же библиотеки тянутся в разных чанках;
  • одинаковые данные запрашиваются несколькими компонентами — каждый «умный» виджет ходит в API сам за собой;
  • сложная навигация и лишние перерендеры — изменение состояния в одном месте вызывает каскад обновлений;
  • тяжелые страницы, где контент и UI смешаны в одном слое — нельзя отделить то, что меняется редко, от того, что дёргается на каждое действие пользователя;
  • отсутствие четкого разделения между статикой, динамикой и интерактивом — всё варится в одном котле.

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

Что именно мы увидели в проблеме

Типовые симптомы

Перед переработкой архитектуры мы фиксировали не абстрактное «всё тормозит», а вполне конкретные признаки. Это важно: когда начинаешь раскладывать проблему на measurable куски, сразу видно, где копать. Вот что мы зафиксировали:

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

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

Как мы оценивали нагрузку

Мы смотрели не только на Lighthouse. Lighthouse хорош для baseline-оценки, но он не показывает реальной картины на продакшене с живыми данными, медленным мобильным интернетом и нагруженным сервером. Поэтому мы собрали свой набор критериев:

  • размер JavaScript на критических страницах — сколько кода реально парсится до первого взаимодействия;
  • долю функциональности, которая реально нужна на первом экране — слайдер ниже складки не должен грузиться вместе с шапкой;
  • количество запросов на загрузке — waterfall в DevTools часто рассказывает больше, чем общий score;
  • время до первого полезного взаимодействия — не когда страница «загрузилась», а когда ей можно пользоваться;
  • повторное использование компонентов и сервисов — или, наоборот, дублирование;
  • сложность поддержки контентных страниц без участия разработчика — если редактор не может выкатить статью без помощи dev-команды, это архитектурный звоночек.

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

Что мы поменяли в архитектуре

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

1. Разделили контентные и интерактивные зоны

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

Мы разделили сайт на три типа зон:

  • статические и почти статические страницы — лендинги, about, контакты, страницы с редко меняющимся контентом;
  • редакционные материалы с умеренной динамикой — статьи, кейсы, дайджесты, подборки;
  • интерактивные сервисные блоки — фильтры, каталоги с сортировкой, рекомендательные виджеты, поиск.

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

2. Пересмотрели стратегию рендера

Не весь сайт должен быть SPA. И не все страницы выгодно отдавать только как чистую статику. Это не религиозный вопрос, а инженерный: какой метод рендера даёт наилучший баланс между скоростью загрузки, актуальностью данных и стоимостью поддержки.

Мы выбрали смешанную модель:

  • для контентных страниц — максимально ранняя отдача HTML, статическая генерация или server-side рендер с агрессивным кэшированием;
  • для страниц с частыми обновлениями — рендер с учетом актуальности данных, инкрементальная регенерация или стриминг;
  • для блоков с низкой ценностью интерактива — отложенная инициализация: блок грузится, когда пользователь до него доскроллил;
  • для тяжелых виджетов — загрузка только по факту необходимости, lazy-импорты и динамические чанки.

Это уменьшило объем работы на клиенте и сократило количество сценариев, где фронтенд делает лишнее. Практически это означало, что критический путь первой отрисовки стал короче на 30-40% для контентных страниц.

3. Упростили композицию страниц

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

Мы перешли к более простой модели:

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

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

4. Убрали лишний JavaScript из критического пути

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

  • слайдеры — они часто ниже первого экрана, а их JS грузится сразу;
  • табы, скрытые за складками — пользователь их не видит, а код уже в памяти;
  • рекомендации ниже первого экрана — алгоритм подбора и рендер виджетов отложены;
  • визуальные эффекты, которые не влияют на понимание контента — параллаксы, сложные анимации;
  • второстепенные виджеты — чаты, popup-формы, социальные кнопки.

Правило простое: если блок не нужен для первого экрана, он не должен тормозить его показ. Технически это решается через dynamic imports и intersection observer — инструменты доступные, но требующие дисциплины в применении.

5. Нормализовали работу с данными

Раньше данные тянулись в разных местах и иногда разными способами. Где-то fetch внутри компонента, где-то глобальный стор, где-то пропсы от родителя — единой логики не было. Это создавало дубли, гонки состояний и сложные баги, которые всплывали только при определённой последовательности навигации.

Мы привели процесс к более понятной схеме:

Задача Раньше После переработки
Получение данных Внутри компонентов На уровне страницы или сервиса
Кэширование Непоследовательное По единым правилам
Повторные запросы Часто дублировались Сокращены
Ошибки загрузки Обрабатывались локально Сведены к общему паттерну
Зависимости UI от данных Смешаны Разделены

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

Как мы снижали нагрузку поэтапно

Этап 1. Инвентаризация

Сначала нужно было честно ответить на вопрос: что реально грузит фронтенд? Не «мне кажется, проблема вот тут», а с цифрами и конкретными кейсами.

Мы составили список:

  • какие страницы самые тяжелые — по размеру JS, числу запросов, времени до интерактивности;
  • какие компоненты используются чаще всего — и сколько кода они тянут за собой;
  • какие скрипты подключаются без необходимости — глобальные зависимости, которые не используются на части страниц;
  • где дублируются данные — одни и те же endpoint-ы вызываются из разных мест;
  • какие блоки можно отложить — ниже складки, не критичные для первого взаимодействия;
  • какие страницы должны быть быстрыми в первую очередь — обычно это точки входа: главная, статьи из поиска, рубрики.

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

Этап 2. Приоритизация

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

Мы выделили страницы, где нагрузка особенно заметна:

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

Сначала оптимизировали именно их, потому что там эффект был наиболее заметен и для SEO, и для пользователей. Core Web Vitals этих страниц прямо влияли на позиции в выдаче, так что приоритет был очевиден.

Этап 3. Архитектурное упрощение

На этом этапе мы убрали лишние слои абстракции. Это болезненный, но необходимый шаг: часто «гибкость» архитектуры оборачивается тем, что код понимают два человека из команды, а изменения занимают недели.

Конкретные действия:

  • сократили число промежуточных оберток — HOC, render props, вложенные контексты;
  • объединили повторяющиеся сценарии — вместо трёх способов отрисовать список сделали один;
  • унифицировали подход к карточкам и спискам — одинаковая структура данных, одинаковый рендер;
  • уменьшили количество мест, где компонент сам решает, откуда брать данные — решение вынесли на уровень выше.

Чем меньше «магии» в коде, тем легче поддерживать производительность. Прямая зависимость: когда новый разработчик может за день понять, как устроена страница, архитектура в порядке. Когда ему нужна неделя — что-то переусложнено.

Этап 4. Контроль регрессий

После изменений важно не обмануть себя. Часто новый код выглядит чище, но весит не меньше. Или весит меньше, но стал медленнее из-за дополнительных уровней индирекции.

Поэтому мы закрепили проверку по контрольным точкам:

  • размер бандлов — смотрим до и после, не только общий вес, но и разбивку по чанкам;
  • критический путь рендера — что блокирует первую отрисовку;
  • число сетевых запросов — waterfall не должен разрастаться;
  • визуальная готовность страницы — когда пользователь видит осмысленный контент, а не скелетон;
  • стабильность при повторной навигации — туда-обратно, с кэшем и без;
  • поведение на слабых устройствах — старый Android на 3G показывает реальную картину лучше любого симулятора.

Эти метрики мы зашили в CI, чтобы каждое изменение проходило автоматическую проверку. Без этого любая архитектурная переработка рискует откатиться через пару спринтов, когда кто-то добавит «ещё один маленький виджет».

Что дало максимальный эффект

Не все изменения одинаково полезны. Мы провели достаточно экспериментов, чтобы понять: косметические правки дают временный эффект, а архитектурные — долгосрочный. На практике лучше всего сработали такие шаги:

  • вынос контента ближе к серверной выдаче — страницы статей стали отдаваться практически готовым HTML, клиенту оставалось только «докрасить» интерактивные элементы;
  • отложенная загрузка второстепенного интерактива — слайдеры, модалки и виджеты загружались только когда пользователь до них доходил;
  • сокращение клиентской логики в статических секциях — header и footer перестали быть «умными» и перестали вызывать ререндеры;
  • устранение повторных запросов — один источник данных для страницы вместо разрозненных fetch-ей внутри компонентов;
  • упрощение структуры страниц — меньше вложенности, меньше оберток, меньше эффектов;
  • единый подход к рендеру карточек и блоков — везде одинаковая механика, одинаковое поведение при загрузке и ошибке.

Если коротко: мы перестали заставлять фронтенд делать то, что он делать не обязан. Браузер хорош для отрисовки интерфейса и обработки пользовательских действий, но плох как вычислительная машина для трансформации данных и сборки страницы из кусков.

Какие ошибки чаще всего мешают снизить нагрузку

1. Пытаться «оптимизировать всё подряд»

Без приоритета легко потратить время на красивые, но бесполезные улучшения. Я видел проекты, где команда месяцами микрооптимизировала анимации, пока главная страница грузилась 8 секунд. Сначала нужно трогать критические страницы и самые тяжелые участки — те, где выигрыш измеряется не миллисекундами, а секундами.

2. Снижать вес JS, не меняя архитектуру

Если просто вырезать пару библиотек, а модель сборки страниц оставить прежней, эффект будет временным. Проблема вернется на следующем этапе роста — через полгода появятся новые фичи, и бандл снова распухнет. Tree shaking и code splitting лечат симптомы, но не причину. Причина — в том, как страницы собираются и кто за что отвечает.

3. Перегружать клиент данными

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

4. Смешивать зоны ответственности

Страница, компонент, сервис и визуальный блок не должны бороться за одну и ту же задачу. Когда компонент карточки сам решает, как получать данные, как их трансформировать и как обрабатывать ошибки — это три разные задачи, смешанные в одном месте. Код начинает дублироваться и расползаться. Через месяц у вас три разных карточки с тремя разными способами работы с данными, и каждая ломается по-своему.

5. Не измерять результат

Без метрик непонятно, стало лучше или просто «кажется, что стало лучше». Субъективное ощущение «вроде быстрее» — плохой советчик. Проверять нужно не по ощущениям, а по конкретным сценариям: время загрузки на медленном 3G, количество запросов, размер бандла, время до интерактивности. И обязательно сравнивать до и после — без baseline-а любое измерение бессмысленно.

Практический чек-лист для похожего проекта

Если сайт начал тяжело жить на фронтенде — страницы тупят, разработка буксует, правки дорожают — проверьте следующее:

  • какие страницы реально самые тяжелые — возьмите данные из мониторинга, а не из предположений;
  • сколько JS нужно для первого экрана — и сколько из этого реально используется до взаимодействия;
  • есть ли дублирующиеся запросы — посмотрите вкладку Network, одинаковые endpoint-ы не должны вызываться дважды;
  • не тянутся ли компоненты в данные по отдельности — каждый виджет сам за себя или есть централизованный слой;
  • можно ли отложить интерактив до действия пользователя — intersection observer и lazy loading творят чудеса;
  • есть ли разница между контентной и сервисной страницей — или всё рендерится одинаково тяжело;
  • не смешаны ли в одном слое рендер, данные и бизнес-логика — компонент не должен знать всё;
  • можно ли часть блоков отдать в более статичном виде — статическая генерация вместо клиентского рендера;
  • измеряется ли производительность после каждого изменения — или «вроде норм» считается достаточным;
  • понятно ли, кто отвечает за структуру страницы — или она собирается стихийно из компонентов.

Пройдитесь по этому списку честно. Если по половине пунктов ответ «нет» или «не уверен» — архитектура точно требует внимания.

Когда такая переработка особенно нужна

Архитектуру фронтенда стоит пересматривать не по календарю, а по симптомам. Вот признаки, которые я считаю критическими:

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

Если хотя бы три пункта совпали, откладывать уже опасно. Дальше будет не «постепенное ухудшение», а лавинообразный рост технического долга, когда каждая новая фича добавляет больше проблем, чем ценности.

Что важно учитывать на сайте с редакционным контентом

Для сайтов вроде Devinco, где есть аналитика, кейсы и дайджесты, отдельная сложность — баланс между медийностью и технической эффективностью. Контентный сайт не может быть просто «быстрым» в ущерб читаемости и редакционной гибкости. Но и редакционная гибкость не должна означать, что каждая статья собирается как мини-приложение.

Нужно, чтобы:

  • статьи открывались быстро — пользователь пришёл из поиска, он не будет ждать 5 секунд;
  • карточки материалов не были перегружены — изображения, мета-информация, превью должны грузиться экономно;
  • навигация по рубрикам работала предсказуемо — фильтры и сортировки не должны вызывать каскад перерендеров;
  • блоки рекомендаций не тормозили основное содержимое — рекомендации важны, но не ценой задержки статьи;
  • редакция могла публиковать материалы без сложной ручной сборки — контент-менеджер заходит в админку, пишет текст, нажимает «опубликовать», и страница работает.

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

Вывод

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

За время этой переработки мы поняли главное: архитектура — это не набор правил на старте проекта, а живая система, которая должна адаптироваться к тому, как сайт растёт. То, что отлично работало для десяти страниц, может стать тормозом для сотни. И умение вовремя это заметить и пересобрать фундамент — возможно, самый важный навык команды, которая работает с долгоживущими проектами.

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

FAQ

Что такое нагрузка на фронтенд простыми словами?

Это объем работы, который браузер должен выполнить, чтобы показать страницу и сделать ее интерактивной: загрузить код, обработать данные, отрисовать интерфейс и поддержать действия пользователя. Всё, что происходит между кликом по ссылке и моментом, когда страницей можно свободно пользоваться — это и есть нагрузка. Чем больше кода нужно скачать, распарсить и выполнить, чем больше данных обработать на клиенте, тем выше нагрузка.

Что дает снижение фронтенд-нагрузки на практике?

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

Можно ли решить проблему только оптимизацией бандла?

Нет. Уменьшение бандла помогает — tree shaking, code splitting, сжатие делают своё дело, — но если архитектура страницы перегружена, эффект будет ограниченным. Можно сократить бандл на 20%, но если страница делает 15 лишних запросов и перерендеривается пять раз до показа, пользователь не заметит разницы. Оптимизация бандла — это часть решения, но не само решение.

Что лучше для контентного сайта: SPA или более статичный подход?

Для большинства контентных страниц лучше более легкий и предсказуемый подход с минимальной клиентской нагрузкой — статическая генерация, server-side рендер, инкрементальная регенерация. SPA оправдан там, где действительно нужен сложный интерактив: дашборды, редакторы, инструменты с实时-обновлениями. Для блога, новостного сайта или портфолио SPA — это избыточное решение, которое добавляет сложности без proportional выгоды.

Как понять, что архитектуру пора пересматривать?

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