JAM stack или как перестать переусложнять разработку веб-сайтов
Веб-сайты бывают разными.
Когда-то давным-давно я разворачивал сайт на CMS Wordpress с целью заработка на контекстной рекламе Google Adsense. У меня был виртуальный хостинг, который за 200-300 рублей в месяц, выделял мне не более 5% процессорного времени далеко не самого мощного сервера. И на этом сервере по мануалам из интернета я устанавливал MySQL, PHP, Apache HTTP Server и Wordpress. Если на сайт заходило больше 30-ти человек, то хостинг на минуту-другую прекращал выдавать ресурсы моему серверу из-за перерасхода лимита, в результате чего сайт был недоступен. Помимо этого раз в месяц на несколько часов хостинг выключался на техническое обслуживание, что тоже приводило к даунтайму. Но тем не менее всё это как-то работало и даже приносило какие-то минимальные деньги.
Не так давно я работал на проекте, где разрабатывался веб-портал, архитектурная схема которого не помещалась на экран монитора. Там было несколько бэкендов, из которых сайт получал информацию, и несколько СУБД с различными кэшами и данными для контента в различных видах. Веб часть была построена на базе крупной CMS, ориентированной на корпоративные масштабы. Всё это разворачивалась в кластере из нескольких серверов в нескольких датацентрах для максимальной отказоустойчивости. Развёртывание происходило автоматизированными скриптами, которые позволяли в случае отказа какого-либо из серверов поднять ему замену за несколько минут. Поверх всего этого был слой из различных мониторингов, служб оповещения и даже группы дежурных администраторов, которые в любое время суток были готовы среагировать на возникшие проблемы и оперативно устранить типовые ошибки.
Даже немного странно, что системы из первого и второго примеров пользователи в итоге называют одинаково – веб-сайт.
Можно ли считать, что сайт из второго примера надёжнее, чем сайт из первого примера?
Да, но только лишь при определённых условиях. А именно при тех затратах на обслуживание и поддержку, которые выделялись на этот портал. При бюджете в 300 рублей на хостинг, он бы даже не запустился, в то время как сайтик с рекламой успешно работал на этих мощностях 80% времени.
Мне всегда казалось, что разработка веб-сайтов в какое-то время свернула куда-то не туда.
Сейчас наличие интернет ресурса – это практически must have для всех, кто ведёт хоть какой-то бизнес, начиная от крупных корпораций и заканчивая самозанятыми мастерами маникюра или репетиторами на дому. При этом разработка самих сайтов превратилась во что-то очень сложное и даже монстрообразное.
Прошли времена, когда веб-разработчику достаточно было знать HTML + CSS и немного JavaScript. Сейчас сайты – это сложные информационные системы с базами данных, бэкендами и фронтендами, построенными на базе различных фреймворков. Всё это нужно как-то уметь разрабатывать, обслуживать и поддерживать.
И если найти деньги на разработку сайта малый бизнес ещё может, то регулярно платить за поддержку и разбор инцидентов готов далеко не каждый.
В результате часто побеждает вариант публикации информации о себе в социальных сетях разной степени запрещённости в РФ или использования конструкторов сайтов, возможности кастомизации которых порой не выдерживают никакой критики.
Но что если всё-таки нужен веб-сайт? Хороший, функциональный, презентабельный, надёжный, но при этом обладающей минимальной стоимостью обслуживания?
Несколько лет назад передо мной встал именно такой вопрос.
В погоне за минимализмом
За годы работы в IT у меня уже выработалась некоторая профессиональная деформация, когда в любом компоненте системы ты в первую очередь видишь точку отказа. Возможно когда-нибудь ИИ разовьётся до таких высот, что сможет создавать безотказные автоматизированные системы. Но пока в разработке таких систем и их компонентов присутствует тот самый “человеческий фактор”, все эти истории с упавшими СУБД, серверами и бэкендами в новогодние ночи были, есть и будут.
В больших корпоративных системах такие проблемы обычно решаются резервированием, когда каждый компонент разворачивается в двух или более экземплярах, позволяя системе пережить падение одного из узлов.
В малых системах такие проблемы обычно решаются верой в лучшее.
Так как хотелось чего-то большего, чем надежды на авось, я начал искать способы разработки сайта с минимально возможным набором компонентов.
Любой сайт – это как минимум какая-то HTML страница, стилизованная с помощью CSS. Еще нужен какой-нибудь сервер, на котором будет крутиться какой-нибудь HTTP сервер а‑ля nginx, который будет отдавать по запросу эту самую HTML страницу.
И… всё!
То есть те самые HTML + CSS + JavaScript (если нужно как-то «оживить» странички), которых когда-то было достаточно для веб-разработки.
Да и что может быть надёжнее, чем простой http сервер, отдающий статику? Даже если этот сайт я положу на флешку и повезу вам лично домой, рисков, что что-то пойдёт не так, будет гораздо больше.
Осталось понять, как всё это разрабатывать в современных условиях. Если сайт будет насчитывать сотни страниц, меньше всего захочется добавлять и обновлять их вручную.
И тут на сцену выходит JAM stack
JAM – аббревиатура от слов JavaScript, API, Markup. Это подход, когда вы при помощи JavaScript получаете данные из какого-то API, накладываете их на разметку страницы и генерируете статический сайт.
Стек технологий, реализующих эту концепцию, называют JAM stack или JAMstack или даже Jamstack.
Ничего непонятно? Давайте разберём на примере.
Допустим, у нас есть сайт с каталогом каких-нибудь товаров. Допустим, у нас на данный момент 50 товаров и мы хотим их постранично отображать по 10 штук.
В обычном подходе при посещении пользователем каталога товаров браузер отправит запрос на сервер, где тот будет перенаправлен на некоторый обработчик, например, написанный на PHP.
Запустится интерпретатор PHP и начнёт выполнять скрипт: возьмёт шаблон страницы каталога, сделает запрос к базе данных для получения информации о товарах, получит, к примеру, первые 10 товаров и информацию о том, что их всего 50, наложит эту информацию на шаблон, сгенерирует итоговую html страницу каталога с товарами и ссылками на следующие 4 страницы каталога и отправит этот html в ответ на запрос браузеру.
В результате пользователь увидит первую страницу каталога, а при переходе на следующую страницу весь процесс повторится, за исключением того, что сервер вернёт данные со следующими 10-ю товарами.
Такой подход называется Server-Side Rendering или SSR.
При другом, не менее распространённом подходе у нас будет одна html страница, на которой будет какая-то общая информация каталога, а также JavaScript код, который после загрузки этой страницы в браузере клиента выполнит запрос на сервер, получит данные о товарах для текущей страницы и общее количество товаров, сгенерирует html код с этими товарами и со ссылками на следующие 4 страницы и подставит этот код в специально заготовленное место внутри страницы каталога.
В результате посетитель так же увидит итоговую страницу каталога с первыми десятью товарами и ссылками на другие страницы каталога, а при переходе на вторую страницу весь алгоритм повторится за исключением того, что JavaScript запросит у сервера второй десяток товаров.
Такой подход называется Client-Side Rendering – CSR.
Можно заметить, что подходы очень схожи, разница только в том, где происходит логика “сборки” итоговой html страницы с данными из БД: на стороне сервера или на стороне клиента (в браузере).
Второй важный момент – вся эта логика выполняется каждый раз при посещении каждого пользователя на каждую страницу каталога. Поэтому нагрузка на сервер и на базу данных растёт с количеством посетителей.
Как этот же процесс будет выглядеть с применением JAM stack?
У нас будет одна шаблонная html страница каталога, в которой примерно так же, как и в предыдущем подходе с помощью JavaScript будет происходить получение данных из API, преобразование этих данных в html и подстановка его в шаблон страницы.
Но вся эта логика будет выполняться не у пользователя в момент загрузки страницы, а один раз на сервере, когда мы запустим процесс генерации итогового «дистрибутива» нашего сайта.
Результатом генерации будет 5 html страниц, на каждой из которых сразу будет содержаться информация о нужных десяти товарах. Далее этот “дистрибутив” сайта разворачивается на сервере, и при переходе пользователя на каждую страницу каталога сервер будет отдавать статическую html страницу с уже готовым контентом.
Этот подход называется Static Site Generation – SSG.
В чём преимущества подобного подхода?
- Уменьшается нагрузка на бэкенд. Теперь к нему за информацией ходит не каждый пользователь и не с каждой страницы, а только генератор один раз при сборке дистрибутива.
- Увеличивается надёжность сайта. В случае недоступности бэкенда в первых двух подходах пользователь увидит унылую информацию о технических проблемах с просьбой зайти позже. С SSG подходом пользователь не заметит никаких проблем и получит доступ ко всей информации, так как она заранее сгенерирована в статические html файлы.
- Снижаются требования к хостингу. Для развёртывания сайта достаточно умения отдавать статический контент. Помимо простых VPS с nginx на борту, это могут быть GitHub Pages, Vercel, AWS S3 или Yandex Cloud Object Storage, стоимость которых в разы ниже, чем стоимость хостингов, на которых нужно разворачивать фронтенд + бэкенд + БД.
- SEO оптимизация. Статическая информация очень хорошо воспринимается ботами поисковых систем. Вдобавок современные SSG фреймворки из коробки могут производить много оптимизаций при генерации: например, оптимизировать картинки или добавить предзагрузку страниц, на которые можно по ссылкам перейти с текущей страницы. В результате переходы по страницам сайта становятся не просто быстрыми, а практически моментальными, что положительно сказывается и на пользовательском опыте, и на оценках вашего сайта поисковыми системами, а значит и на более высоком его ранжировании.
Стоит отметить, что данные при генерации не обязательно получать из какого-то веб-сервиса. Источником контента могут быть markdown файлы, хранящиеся рядом с кодовой базой сайта.
Например, этот блог построен как раз по такому принципу: новые статьи добавляются в виде markdown файлов в репозиторий с кодом сайта, изменения пушатся на GitHub, запускается сборка сайта с помощью GitHub Actions и результат публикуется на GitHub Pages. Всё быстро, надёжно и бесплатно 🙂
Если же с контентом работает человек, который ничего не знает про markdown и репозитории, то для него можно развернуть так называемую headless CMS – систему управления контентом, которая предоставляет удобный интерфейс работы с данными и умеет отдавать эти самые данные генератору сайта через API.
Хорошим примером тут может быть open source CMS Strapi.
Да, эту CMS нужно будет куда-то развернуть, но, как я уже писал выше, требования к отказоустойчивости такой админки будут в разы меньше: её падение повлияет только на возможность публикации нового контента, в то время как пользователи продолжат успешно пользоваться самим сайтом.
Другой вариант – воспользоваться услугами облачных headless CMS, например, Storyblok. За не очень большие деньги получаешь и дружественный интерфейс работы с контентом, и надёжность/высокую доступность такой системы.
Ложка дёгтя в бочке мёда
Наверняка внимательный читатель к этому моменту задаётся вопросом: а что делать с различными динамическими функциями, которым всё-таки нужен бэкенд и база данных в реальном времени.
Это могут быть какие-нибудь оценки товаров, форма обратной связи, оформление заказа и т.п.
Конечно статический сайт не позволит реализовать такие вещи.
Фреймворки, реализующие JAM подход, позволяют разделять ваш JavaScript код на «серверный», т.е. тот, который будет исполняться во время генерации сайта (получение данных, разбиение их на страницы и т.д.) и на «клиентский» – тот, который будет исполнен в браузере у пользователя.
Таким образом на статическом сайте могут существовать динамические компоненты, взаимодействующие с бэкендом, например, для сохранения оценки товара или для заказа.
И сейчас вы можете разочарованно развести руками: как же так, и вот снова нам нужен бэкенд, и вот снова нам нужна СУБД, и снова всё это нужно как-то поддерживать и заботиться о доступности. Зачем тогда было городить весь этот огород с генератором?
Да, это не серебряная пуля.
Но давайте хорошенько подумаем: а как часто нам действительно нужны динамические функции?
Я не зря взял пример магазина с товарами, хотя, казалось бы, это неподходящий сценарий для статических сайтов.
Если мы разрабатываем маркетплейс или интернет-магазин для крупного ритейлера, где список товаров и информация о них может меняться быстрее, чем будет генерироваться новая версия сайта, то действительно вряд ли стоит смотреть в сторону Jamstack.
Но что если это сайт для небольшой пиццерии или булочной с устоявшимся ассортиментом? Или магазинчик мёда с собственной пасеки?
Можно ли считать сайт мастера, оказывающего парикмахерские услуги на дому, слишком динамическим для статической генерации?
Или всё-таки информация о мастере, его услугах, фотогалерея его работ – это статика, и лишь одна функция динамическая – запись на стрижку?
И это я не говорю о тысячах лэндингов, статейных сайтов и блогов, которые являются идеальными кандидатами на переход с какого-нибудь Wordpress на SSG фреймворк.
Для каждой динамической функции потребуется свой бэкенд-сервис. Но вынося его в самостоятельный компонент, отделённый от статического сайта, вы немного двигаетесь в сторону микросервисной архитектуры, про которую все так любят говорить, но мало кто использует.
Это значит, что потеря работоспособности одной функциональности может не повлиять на другую функциональность. И уж тем более не повлияет на доступность всего сайта.
Упал сервис комментариев? Не беда, посмотреть товары и купить их всё равно можно.
Упал сервис заказа товаров? Плохо, но клиент всё ещё может посмотреть на ассортимент товаров и, найдя понравившийся товар, либо зайти позже, либо позвонить по телефону и оформить заказ через оператора.
И получается, что если внимательнее посмотреть на функции того или иного сайта, то подходящих под Jamstack сценариев будет гораздо больше, чем только лэндинги и блоги.
Пару слов об SSG фреймворках
Так как разработка статического сайта очень похожа на разработку динамического CSR сайта (в первом случае код выполняется на сервере во время генерации сайта, во втором случае – у клиента в браузере), то и вести такую разработку удобнее не на чистом JS, а с использованием какого-нибудь JS фреймворка.
Практически для каждого популярного варианта найдётся свой фреймворк (порой даже не один), реализующий SSG:
- Next.js, Gatsby.js для React
- Nuxt.js, VuePress для Vue
- SvelteKit для Svelte
- Scully для Angular и т.д.
Но абсолютной жемчужиной в этой экосистеме, на мой взгляд, является фреймворк Astro, который вобрал в себя всё самое лучшее, что было в других фреймворках, и предложил возможность использовать любой JS фреймворк (или даже чистый JavaScript) в зависимости от предпочтений разработчиков.
Про Astro, надеюсь, я напишу отдельную статью.
А про Jamstack у меня всё.
Спасибо.