Как битриксоиды в React уходили

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Приятно познакомиться, мы битриксоиды. Да-да, те самые которые:

  • вообще не модные,

  • пишут НЕ на Laravel и Symphony,

  • возятся с кучей мягко говоря “неидеального” кода под названием “1С-Битрикс: Управление сайтом”,

  • проходят Академию 1С-Битрикс и сдают платные экзамены для подтверждения компетенций,

  • умеют дорабатывать обмен с 1С без истерики,

  • берут с заказчика "тонну денег" за то что любой php-джун сделает на вордпрессе одной левой,

  • но при всем этом почему-то делают сложные проекты переживающие тысячи доработок без потери товарного вида и управляемости.

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

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

очередная переделка сайта
очередная переделка сайта

Для сайта www.intervolga.ru это пятая инкарнация. Вообще по версиям нашего сайта можно отслеживать ключевые вехи в развитии WEB-разработки ))

версии сайта
версии сайта
  • 2003 г. Статичная верстка, добавление страниц силами верстальщика/программиста. Прямо как в мемуарах дедушки Лебедева ))

  • 2007 г. Самописный php-движок. В те бородатые годы свою CMS не изобретал только ленивый.

  • 2012 г. Первая версия нашего сайта на 1С-Битрикс. Как раз незадолго до этого ИНТЕРВОЛГА стала делать (страшное по тем временам святотатство) сайты на платных CMS (1С-Битрикс и UMI).

  • 2015 г. Сайт отличался дизайном, структурой и главное — адаптивностью. На бекенде все еще был 1С-Битрикс.

  • И наконец весной 2022 мы запустили текущую версию на связке React с серверным пререндерингом и Битриксом в качестве API.

Предпосылки

Я хорошо помню момент, когда в веб-разработку стали проникать модные и по сей день фронтенд-фреймворки: Angular, React, Vue…  Это было одновременно интересно и непонятно. Непонятно “как” это применять в нашей обычной работе и главное “зачем”.
В те времена не было дико сложных интерфейсов, но уже была адаптивность. А основными вызовами было делать сайты так, чтобы:

  • после пары правок не появлялись дубли jQuery и его плагинов,

  • во всех браузерах (тогда было много движков, не только Chromium) сайт выглядел одинаково,

  • сайт адаптивился в IE9.

Чтобы противостоять этим вызовам мы плотно сидели на Bootstrap и БЭМ (только не на bem-tools, а на webpack со всеми необходимыми лоадерами, которые сами же портировали: bemjson, bemdecl, bemdeps и пр.).

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

Больше всего из этого списка привлекала стоимость поддержки. По нашему опыту очень много календарного времени и ошибок в процессе поддержки больших проектов возникает при изменениях в дизайне и как следствие верстке. Вот изменил дизайнер какую-нибудь кнопочку, а на сайте ее верстка используется в 20 шаблонах. Нужно все места найти, везде исправить, все протестировать.

Со временем интерфейсы становились сложнее. А клиенты хотели вынести в WEB все больше своих задач, которые ранее решались в desktop-приложениях. Например, таблицы с заказами сгруппированными по 1-2-3 полям и всевозможными настройками сортировки/фильтрации и т.д.

Делать это на jQuery было тяжко и так мы стали знакомиться с новым миром, в котором уже были продвинутые компоненты React для этих задач.

А потом были проекты со сложной логикой взаимодействия, асинхронностью, рисованием схем в Яндекс.Картах... Так в нашу жизнь вошел Redux (часто как самостоятельная единица).

Вот так по чуть-чуть с каждым следующим проектом в повседневную работу стали проникать frontend-фреймворки и их экосистема.

Фронтенд-фреймворки для олдскул-сайтов и бизнеса

В отличие от стартапов, которые привнесли JS-фреймворки в нашу жизнь, мы делаем веб-проекты (сайты, личные кабинеты, B2B-интернет-магазины) с оглядкой на реальное использование. А практика показывает, что хоть 10 лет назад, хоть сейчас, хоть через 10 лет самым дешевым каналом рекламы будет SEO. Да-да, то самое SEO, в котором куча ритуалов и ничем не обоснованных и противоречащих здравому смыслу требований к сайтам.

И будь ты хоть трижды лауреатом премий по веб-разработке, но когда с одной стороны ты говоришь заказчику что “добавление .html к адресам страницы” не даст никакого прироста в поисковиках, а с другой стороны сеошник говорит, что без этого “сайт никогда не окажется в топе и конкуренты соберут все сливки”, заказчик будет давить именно на тебя. Уверен Вы тоже сталкивались с таким карго-культом. Сеошник для разработчика – самый придирчивый клиент.

Собственно к чему я это все: фреймворки — добро, но сайт, который нельзя рекламировать самым дешевым способом, мало кому нужен. А почему нельзя? Потому что поисковики индексируют html и текст. А с фреймворками AS IS никакого текста на странице которую получит поисковый бот не будет.

И да, это можно решить разными хитростями (например, поддерживать 2 версии и заниматься клоакингом). Но когда уникальных страниц у вас десятки и нужно их дорабатывать, то все эти приемы становятся “костылями в колесах на полном ходу”. А индексировать с исполнением JS поисковики хоть и умеют, но доля таких индексаций минимальна (потому что вас много, а процессорное время у Google/Яндекс дорого и ограничено).

Вот получается, что для коммерческого сайта, выполняющего бизнес-задачи на широкую аудиторию и использующего SEO, есть всего 3 варианта действий с React/Vue:

  • не использовать совсем (и так делается подавляющее большинство сайтов);

  • использовать только в неиндексируемых местах со сложными взаимодействиями (умный фильтр, корзина, форма заказа, опросник со взаимозависимыми полями, калькулятор чего-нибудь);

  • использовать везде,  но делать серверный пререндеринг.

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

Серверный пререндеринг

Лично мне известно 3 варианта делать серверный пререндеринг.

№1. Добавить в php модуль V8 и исполнять JS прямо в шаблонах.

mod_v8
mod_v8

С одной стороны этот вариант ломает Битрикс меньше всего: нужно только подменить шаблонизатор. С другой — по отзывам является достаточно медленным и вызывает много вопросов о взаимодействии разных React-компонентов между собой и дедупликации / tree sharking js-кода.

№2. Использовать NodeJS как gateway для рендеринга.

NextJS
NextJS

Самый часто используемый в настоящий момент вариант. Весь фронтенд выделяется в 2 сущности: фронтенд исполняемый в браузере и так называемый “легкий бекенд” который бегает в “тяжелый бекенд” за данными. Из недостатков: нужно писать API.

№3. Использовать сервисы/софт (например, prerender.io) делающие пререндеринг для поисковых ботов.

prerender.io
prerender.io

Пререндеринг делается (предположительно) за счет запуска Headless Chrome на сервере. Пожалуй самый ресурсоемкий вариант и решиться на него можно только в крайнем случае или если контент на сайте редко меняется. Выполнение страниц "в браузере на сервере" создает большую нагрузку и является бутылочным горлышком. Чтобы снизить нагрузку пререндеринг выполняется только для ботов поисковых систем (определяются по User-Agent) и кешируется. Но что будет если у вас посещаемый интернет-магазин, в котором и ассортимент и цены меняются довольно часто?..

Мы пошли по второму пути. К моменту начала работ компетенции в React у нас было больше чем во Vue. В качестве фреймворка для серверного пререндеринга взяли NextJS, который и сегодня является выбором по умолчанию в таком сетапе. Для параллельной обработки нескольких запросов используем pm2 в режиме “cluster”.

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

Например, при использовании Redux приходится инициировать загрузку данных на уровне страницы (getServerSideProps) и дожидаться полной инициализации stor-а перед рендерингом. В демках, где нет большой глубины вложенности контейнеров и компонентов, это выглядит приемлемо. А вот на реальных больших/глубоких страницах — не очень.

Ну и вишенка на торте: скорость. Как только вы начинаете рендерить страницы на сервере, вы мгновенно упираетесь в то, что рендеринг вообще-то говоря не быстрый. Из 0.5 секунд генерации страницы на него уходит около 80%. Кому интересно погрузиться в историю вопроса — см. https://www.youtube.com/watch?v=PnpfGy7q96U 

Вообще нам бы очень хотелось найти такое решение для NextJS, которое кешировало бы финальный html на основе хеша stor-а (состояния из которого отрисовывается страница). Но такого, к сожалению, не нашлось. Единственное, что отдаленно похоже — Next Boost, но он кеширует по времени, а не по "слепку данных".

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

Посадочные страницы

Много лет назад нам потребовалось дать своим маркетологам инструмент для создания 1000 и 1 посадки без помощи программиста. Ничего готового тогда не нашлось и в 2016-2018 году мы сделали собственный конструктор посадочных страниц на основе drag&drop-редактора писем, который только что появился в 1С-Битрикс. Все необходимые блоки мы нарисовали/сверстали. 

Самописный редактор посадочных страниц
Самописный редактор посадочных страниц

К моменту переезда на этом механизме было собрано более 50 посадок. Штука была хорошая, но к 2020 она крепко устарела.

Когда встал вопрос про посадки на новом сайте, решили поискать другое решение (поддерживать самопис, который периодически отваливается после обновлений Битрикса никому не хотелось). И выбор по сути был невелик: Сайты24 или Тильда.

Сайты24 — конструктор посадочных созданный специально для Битрикс24 и позже появившийся в CMS. Модуль весьма хорош с точки зрения архитектуры. Приятный редактор, продуманное структурирование блоков, несложное добавление новых.
Но вот имеющиеся по умолчанию блоки — просто ужас. Собрать на них что-то пристойное и тем более mobile-friendly — квест не для слабонервных. Мы имели негативный опыт в коммерческих проектах. И делали эксперимент: два менеджера независимо друг от друга собирали одни и те же посадки на обоих инструментах. Черновик в обоих инструментах собирался одинаково быстро, но в Тильде черновик сразу же был пригоден к публикации, а в Сайтах24 на танцы с бубнами уходила еще куча времени.

В общем, если Вам нужен собственный конструктор и вы хотите/готовы нарисовать и сверстать блоки — крайне рекомендую Сайты24. Ну а мы не хотели :)

Остановились на Тильде. Маркетологи собирают страницы, Битрикс забирает готовый HTML. Фронтенд на React показывает этот HTML в качестве контента страницы. 
Интеграция работает через готовый плагин, у которого вскрылся фатальный недостаток. Каждый раз когда маркетологи делают новую посадку они сбрасывают кеш. Плагин после этого загружает все страницы по API заново. И мы с нашим количеством посадок очень быстро упираемся в лимит запросов. Поэтому прямо сейчас готовим к релизу собственную интеграцию, которая скачивает только изменившиеся страницы, а не все подряд.

Больше всего проблем было с нашим левым меню. Тильда рассчитывает, что страница будет опубликована “как есть”. А у нас она соседствует с левым меню. Как результат – некоторые блоки неверно считали свою ширину и “съезжали”. Но нашелся “бабуличий лайфхак”. Если до инициализации скриптов тильды создать глобальную переменную zero_window_width_hook с значением <some_node_id>, то после инициализации в расчетах макета будет фигурировать не ширина окна, а ширина блока-контейнера с ID <some_node_id>.

“Почти REST” API

Фронтенд должен получать данные для отрисовки. Те самые данные, которые раньше получал шаблонизатор Битрикса. Для написания API есть несколько опций:

  • Использовать модуль REST, входящий в “1С-Битрикс: Управление сайтом”.

  • Взять сторонний каркас для построения API с блекджеком вроде API Platform, Apigility, Fusio.

  • Писать свои Битрикс-компоненты, снабжая их ajax-контроллером.

При переборе вариантов сразу вспоминаем, что у Битрикса нет высокоуровневого API уровня бизнес-логики. Только низкоуровневое (пример— извлечение записи из таблицы/инфоблока). А все хитрые операции вроде кеширования, вычисления значений SEO-тегов, определения доступных типов цен и расчета скидок (в случае торгового каталога) делают компоненты. Но львиная доля даже самых популярных компонентов все еще написана без использования классов. Значит унаследовать реализацию логики подготовки данных не получится.

Для понимания масштаба вопроса: компонент, выводящий произвольный список новостей, занимает порядка 600 строк кода. Помимо основного кода там конечно же есть валидация настроек, парсинг ЧПУ и т.д. А представьте сколько нужно кода чтобы сделать REST API для управления корзиной или оформления заказа?

Копировать, рефакторить и главное поддерживать большую кодовую базу ради “эстетически красивого” API не сильно хотелось. Тем более, что весь нужный код (оставим вопрос его красоты и качества за скобками) уже написан — в компонентах. Нужно лишь элегантно его оттуда достать…

Решением стало написание своей “запускалки” компонентов внутри встроенного модуля REST.

Стандартная “запускалка” — $APPLICATION->IncludeComponent(...) рассчитана на такое выполнение компонента при котором весь формируемый HTML попадает в стандартный поток вывода, скрипты/стили/SEO-теги — в отдельное хранилище для постановки на страницу и т.д.

Наш аналог IncludeComponent:

  • отключает встроенный шаблонизатор (убирает паразитную нагрузку),

  • возвращает в ответе параметры и данные (arParams и $arResult), которые ранее поступали в шаблонизатор,

  • возвращает в ответе все метатеги и статику, которые компонент пытался добавить на страницу,

  • удаляет все что нельзя сериализовать в json (например объект постраничной навигации),

  • удаляет все небезопасные данные (ключи которых в $arResult начинаются с ~),

  • удаляет все редко используемые поля (сортировка и xml_id свойств, тип файла для свойств типа “файл” и т.д.) для уменьшения размера REST-ответа,

  • получившийся ответ отдает во фронтенд через стандартный модуль REST.

Запуск компонента в REST. Начало...
Запуск компонента в REST. Начало...

С одной стороны такое решение выглядит странно и вообще не по канонам. С другой — API для списка новостей делается (без преувеличения) 5 минут. И еще 5 минут на детальную страницу. И так далее…

Вызов стандартных компонентов в REST
Вызов стандартных компонентов в REST

Есть конечно и исключения. Например, компонент управления подписками на рассылку пришлось переписать пару раз. Но если брать по среднему — время экономится существенно.

А еще в качестве “псевдо-документации” для фронтенда можно использовать те самые php-шаблоны, которые мы отключили. Фронтенд в ответе API получает ровно то, что получали эти шаблоны. А значит можно подсматривать в них чтобы понять, что/где и почему нужно выводить.

Отдельно хочется чтобы разработчикам компании 1С-Битрикс икалось почаще до тех пор пока модуль REST кодирует JSON-ответ без флага JSON_UNESCAPED_UNICODE. Из-за этого любой кириллический текст раза в 2 раздувает размер ответа в килобайтах …

Ну и еще 1 прикол, с которым пришлось побороться.
Стандартный модуль REST в лучших традициях заботы о производительности вместо настоящих сессий использует “виртуальные”, которые никуда не сохраняются и как следствие не блокируют параллельные запросы от одного клиента. Разумеется только для неавторизованных пользователей. И за это разработчикам лайк.

Но вот компоненты… бывают очень завязаны на сессии. Например, компонент рейтинга (оценка “звездочками”) запоминает голосовали Вы или нет как раз в сессиях.
Для “таких вот” компонентов пришлось изобрести механизм получения ID своей настоящей сессии, а потом еще и пропихивания его в запросы компонентов которым настоящая сессия нужна.
Зато если мы отправляем 10 запросов к API которым не нужна авторизация — все они будут выполняться параллельно. GraphQL был бы безусловно лучше, но это тема для отдельной истории…

Оптимизации и трюки

Один из важных параметров оптимизации скорости загрузки — картинки. Они не должны быть слишком большие ни по разрешению, ни по объему.

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

А вот для уменьшения размера в байтах используем кодирование в WebP с небольшой хитростью. Все webp-версии картинок называются также как и исходные, но с добавлением расширения “.webp”. Для отображения мы используем модифицированный компонент next/image из NextJS. От стандартного он наследует возможность автоматического выбора нужного разрешения. А в случае если браузер не поддерживает WebP — загружает картинку в исходном (чаще JPEG) формате просто удаляя расширение “.webp” из адреса.

JPEG -> WEBP -> JPEG
JPEG -> WEBP -> JPEG

Плюс ко всему мы используем ленивую загрузку изображений через IntersectionObserver.

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

Плагины вроде Redux Saga позволяют нам виртуозно жонглировать AJAX-запросами. Например, в поиске если пользователь что-то набрал и запрос успел отправиться в API а затем пользователь продолжил набор — мы отменяем старый запрос.

Отмена AJAX-запроса
Отмена AJAX-запроса

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

Трудоемкость и эффект

Предыдущую версию нашего сайта мы сделали за 500-700 часов со всеми потрохами (начиная от проектирования и дизайна, заканчивая тестированием и чисткой контента).

На новую версию ушло 2 500 человеко-часов. Тут стоит учесть:

  • Архитектуру фронтенда переделывали 3 раза, постепенно осознавая “как надо”.

  • Ресурсы на сайт мы тратили по принципу “нет коммерческих задач — иди поделай новый сайт”.

  • В указанное время включена переделка 50+ посадочных страниц.

Но если убрать все лишнее, я бы сказал, что чистая трудоемкость разработки React-SSR-сайта в 2 раза выше чем “на обычных технологиях”.

Вы можете спросить “а зачем тогда вот это все?”. А вот зачем:

  1. Несмотря на большую трудоемкость и стоимость разработки, поддержка и доработка такого решения оказывается проще.

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

  3. С обычной версткой после пары десятка правок лучше даже не смотреть на PageSpeed.

Внимательный читатель может спросить “а чего это у вас PageSpeed не 149%”?

PageSpeed компьютер
PageSpeed компьютер
PageSpeed телефон
PageSpeed телефон

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

Быть или не быть

Пожалуй все зависит от типа проекта, планируемого срока его жизни и бюджета. В поле моего внимания в основном интернет-магазины и разного рода B2B-кабинеты, поэтому буду говорить именно про них. 

Для B2B-кабинетов, где не нужен серверный пререндеринг (в идеале все не-посадочные страницы должны быть доступны только после авторизации) — однозначно переходить. Последние три года все такие проекты мы делаем именно на Vue с Битриксом в качестве API.

Для B2B-кабинетов которые показывают каталог и “базовые” цены до авторизации и интернет-магазинов нужно ориентироваться на срок жизни и бюджет. 

  • Если проект делается чтобы быть среди лучших, является одним из основных источников заказов (наравне с оффлайн-магазинами) и будет поддерживаться/развиваться много лет — скорее стоит. На длинном горизонте высокая начальная трудоемкость окупится более простой поддержкой.

  • Если нужен интернет-магазин “как можно быстрее и дешевле, а потом будет видно стоит ли в него вкладываться — однозначно стоит отбросить все эти новомодные веяния и делать проект с обычной версткой.

Источник: https://habr.com/ru/company/intervolga/blog/698536/


Интересные статьи

Интересные статьи

Подробностями разработки онлайн-платформы выполнения и компиляции кода более чем на 40 языках делимся к старту курса по Frontend-разработке. Автор этого материала — основатель TailwindMasterKit.
Совсем недавно состоялся релиз 6-ой версии react-router. Вообще создатели react-router часто меняют подходы, используемые в библиотеке, но в этот раз они объединили лучшее, что было в прошлых версиях....
Недавно, я наткнулся на имплементацию хуков для Flutter, о которой и хочу рассказать. Что там с хуками? Всего голосов 1: ↑1 и ↓0 +1 Просмотры 36 Добавить в закладки 0
Проблема оптимизации useContext и интересный способ её решения. Мы создадим свой кастомный хук создать useSmartContext с такой же сигнатурой как у useContext, но кот...
Автор статьи, перевод которой мы сегодня публикуем, предлагает React-разработчикам отойти от использования create-react-app (CRA) и создать собственный шаблон для React-приложений. Зд...