При создании корпоративных сайтов или аналогичных целевых страниц распространенным инструментом является Wordpress, динамическая платформа для ведения блогов, которую можно настроить для создания полнофункциональных веб-сайтов. Wordpress позволяет создателям контента очень легко добавлять новые статьи или страницы через простую консоль администратора. Однако Wordpress (или любая другая подобная платформа CMS) также имеет некоторые проблемы, которые, как мы считаем, плохо подходят для корпоративного веб-сайта:
- Трудно заставить его работать быстро. Wordpress требует наличия сервера приложений PHP (например, Apache, FastCGI) и базы данных MySQL для обеспечения его динамической природы. Хотя существует множество методов для быстрой загрузки такого динамического сайта (например, кэширование с помощью Varnish), они значительно усложняют обслуживание системы.
- Трудно настроить. Настройка обычно начинается с темы, которая имеет свои собственные компоненты, которые вы можете настроить для создания собственного фирменного стиля. С точки зрения веб-разработчика, создание уникального фирменного стиля на основе существующей структуры темы обычно сложнее, чем создание веб-сайта с нуля. Вот почему существует целая под-профессия программной инженерии, посвященная настройке Wordpress.
- Трудно поддерживать. Как правило, сложно обновлять версии Wordpress или выполнять операции с базой данных MySQL без простоя.
- Трудно обеспечить. Wordpress - это полностью динамический сайт со встроенной функцией входа в систему для использования консоли администратора. Это создает дополнительную поверхность для потенциальных атак на безопасность (многие сайты пострадали от атак из-за плохо настроенного Wordpress).
Ничто из этого не нарушает договоренности - многие компании успешно управляют сайтами Wordpress, решая все эти проблемы. Однако их решение требует времени, усилий и затрат на долгосрочное обслуживание. Мы считаем, что это неэффективное использование ресурсов для разработки большинства корпоративных сайтов. Wordpress отлично подходит, когда у вас есть много писателей, публикующих много статей в день, но динамические функции тратятся впустую для сайта, который настраивается один раз с периодическими публикациями PR-новостей.
С другой стороны, статический сайт легко обслужить с помощью службы хостинга, такой как Firebase, требует небольшой дополнительной работы для обеспечения производительности и масштабируется для любого количества пользователей. Хотя не существует жесткого практического правила, мы считаем, что сайты с не более чем одним новостным сообщением в неделю и, возможно, даже с одним новостным сообщением в день, лучше обслуживаются статическим сайтом, чем системой CMS.
Создание статического сайта с помощью стандартных веб-инструментов
Когда компании решают создать чисто статический сайт со случайным новым контентом, они часто используют так называемый генератор статических сайтов - поиск в Google даже вызывает сайт, в котором перечислено огромное их количество. Популярные инструменты в этой категории включают Jekyll и Hugo.
Проблема с такими генераторами статических сайтов заключается в том, что каждый из них имеет свои собственные макеты каталогов и инструменты сборки - даже для опытных веб-разработчиков это все равно, что изучать новый инструмент с нуля. Хуже того, библиотеки и передовые методы, которые вы можете использовать в стандартной веб-разработке, обычно не применяются или их сложно применить к этим настраиваемым инструментам. И последнее, но не менее важное: интернационализация, как правило, требует большого количества дублирования кода (мы хотели добавить японский перевод сайта с этим редизайном).
Мы разрабатываем веб-сайты сервисов с использованием современных веб-технологий, включая Webpack, React и styled-components, с использованием мощного React Boilerplate. Поскольку у нас есть большой опыт работы с этой структурой и создания эффективных веб-сайтов, мы хотели иметь возможность повторно использовать эти методы при создании статического сайта, разрабатывая его поверх React Boilerplate.
Реагировать на статическую целевую страницу ?!
Первая реакция, которая может возникнуть у некоторых, - это шок от того, насколько безумно даже думать об использовании React на статической целевой странице. Мы только что говорили о том, как мы хотим сделать сайт максимально быстрым, но теперь мы предлагаем обязательную загрузку и выполнение довольно большого файла Javascript, прежде чем мы сможем увидеть что-либо на странице. Генераторы статических сайтов, такие как Jekyll, создают простые HTML-файлы, которые браузеры могут обрабатывать очень быстро. Стандартное приложение React никогда не сможет сравниться со скоростью рендеринга этих статических страниц.
Введите static-site-generator-webpack-plugin. Этот плагин позволяет выполнять предварительный рендеринг сайтов на основе веб-пакетов. Он просто передает URL-адрес определяемой пользователем функции Javascript, и любая возвращаемая строка выводится в файл - это отлично работает с React, который имеет первоклассную поддержку рендеринга на стороне сервера (SSR). Хотя может показаться странным использовать SSR во время процесса сборки (где находится сервер?), По идее нет никакой разницы между рендерингом HTML на сервере NodeJS и рендерингом HTML во время вызова веб-пакета NodeJS.
В конфигурации вашего веб-пакета просто добавьте плагин со списком маршрутов на вашем сайте:
и тогда в prerender.js может быть что-то вроде этого:
Здесь много строк, но основной поток состоит в том, что мы вставляем предоставленный URL-путь в начальное хранилище redux, чтобы он отображал правильный маршрут. Затем мы вызываем renderToString
, чтобы получить полностью визуализированную HTML-строку для тела вместе с соответствующими вызовами для предварительного рендеринга <head>
содержимого из Helmet и styled-components, а затем передаем его простому компоненту шаблона, который визуализирует <head>
и <body>
вместе. Благодаря этому мы можем получить каталог полностью визуализированных HTML-страниц, готовый для успешного отображения браузером без необходимости выполнения Javascript.
Динамический контент
Хотя наши потребности в динамическом контенте не так высоки, как в новостном сайте, нам все же нужен относительно простой способ, позволяющий не инженеру добавлять новостные статьи. Поскольку у нас есть полный контроль над процессом сборки (это обычный Javascript), мы можем добиться этого с помощью небольшого количества кода и полезного встроенного плагина webpack: DefinePlugin
. Многие сайты веб-пакетов будут использовать этот плагин для установки среды NodeJS (т. Е. Производства или разработки) в константу для ссылки в коде на поведение, зависящее от среды, но это также можно использовать для установки любой конфигурации, необходимой на сайте. Это означает, что хотя наша бизнес-логика Javascript, которая предназначена для запуска в браузере, не имеет возможности сканировать файловую систему для определения содержимого папки, мы можем выполнить сканирование в самом скрипте веб-пакета и передать информацию нашему бизнес-логика через DefinePlugin
.
В нашей конфигурации веб-пакета у нас есть этот небольшой фрагмент для сканирования каталога:
а затем мы просто передаем его в бизнес-логику:
Константы, установленные DefinePlugin
, оцениваются статически, что означает, что они могут участвовать в таких вещах, как операторы webpack require
. Поскольку бизнес-логике известно имя каталога (slug
в приведенном выше примере), она может просто использовать его для импорта информации из известных имен файлов (например, require(``${slug}/index.md``)
, require(``${slug}/thumbnail.jpg``)
), и все будет объединено.
Мы используем Markdown для содержания новостной статьи, так как это простой для написания структурированный язык разметки, который не-инженеры могут легко составить.
Итак, чтобы добавить новостную статью, все, что нужно, - это добавить каталог в базу кода, и этот каталог будет автоматически распознаваться сканированием каталогов во время следующей сборки. Мы управляем всем нашим кодом, включая этот сайт, в репозиториях GitHub, а веб-интерфейс GitHub достаточен для того, чтобы даже не инженеры могли легко вносить свой вклад. Создатели контента сначала создают каталог локально с помощью редактора Markdown, который также позволяет им предварительно просмотреть результат, а затем они просто перетаскивают каталог в репозиторий в веб-интерфейсе GitHub.
Поскольку нет никакой глобальной конфигурации, только несколько каталогов, которые просматриваются, несколько пользователей могут добавлять контент одновременно без конфликтов. Если бы конфликты были возможны, разрешение конфликтов, вероятно, сделало бы этот подход неприменимым, но поскольку каталоги именуются с использованием желаемого URL-адреса, который по сути уникален, вероятность их конфликта отсутствует.
Ешь свой торт и тоже ешь
Описанный выше процесс создает набор статически отображаемых HTML-страниц. Разве это не означает, что мы теряем все приятные функции React, такие как навигация по страницам без обновления браузера? К счастью, это не так, потому что мы используем стандартные функции SSR React. Это означает, что после того, как HTML страницы загружен и отрисован, наш Javascript все еще может запускаться и монтировать себя на предварительно отрендеренную модель DOM, и после этого он работает точно так же, как если бы мы вообще не выполняли предварительную визуализацию. Навигация по страницам выполняется мгновенно в Javascript с помощью React-Router, и это также позволяет нам иметь переходы CSS при навигации по страницам. Чтобы эта навигация по страницам происходила как можно быстрее, мы не используем функцию разделения кода webpack для большей части сайта, то есть мы используем стандартный оператор статического импорта ES6 (например, import PageA from 'pages/PageA'
), а не динамический импорт. функция (например, import('pages/PageA')
). Это означает, что весь сайт упакован в связанный файл Javascript. Многие одностраничные приложения (SPA) используют разделение кода, чтобы минимизировать размер начальной загрузки пакета, но нам это не нужно здесь, потому что у нас уже есть весь предварительно обработанный сайт в дополнение к полному пакету Javascript.
Да, пакет довольно большой, но даже до того, как браузер завершит загрузку и выполнение пакета Javascript, сайт полностью функционален - все ссылки по-прежнему работают с использованием стандартной навигации по страницам браузера. Это не мгновенно или с переходом через CSS, но это не хуже, чем если бы мы изначально не создавали SPA. Таким образом, мы получаем возможность обслуживать SPA без ущерба для скорости реакции при первой загрузке.
Оптимизация изображений
Поскольку мы используем обычный веб-пакет для нашей сборки, вместо настраиваемой структуры генератора сайтов, мы получаем доступ ко всей экосистеме веб-пакетов. Сюда входит изящный инструмент под названием затяжной загрузчик. Используя его, можно указать размеры изображений рядом с вашей разметкой при импорте, используя что-то вроде:
Вашему дизайнеру не нужно готовить адаптивные изображения! Это происходит автоматически во время сборки веб-пакета, и при добавлении этого в конфигурацию веб-пакета:
вы можете автоматически сгенерировать версию изображения WebP, лучший на данный момент формат сжатия изображения, вместе с JPEG или PNG в зависимости от того, должно ли изображение быть без потерь или с потерями. Позволяя дизайнерам экспортировать как PNG и динамически сжимать в соответствующий JPEG, можно избежать ухудшения качества изображения из-за многократного изменения размера. Кроме того, мы используем простой компонент React, который принимает этот импорт и отображает тег Picture
с источниками как для WebP, так и для JPEG / PNG. Это означает, что в Chrome вы будете обслуживать WebP, а в других браузерах вы получите менее оптимизированное изображение (по крайней мере, до тех пор, пока они не будут поддерживать WebP). Все это происходит автоматически, а это означает, что дизайнерам не нужно беспокоиться об экспорте такого количества файлов, а пользователи всегда получат изображения наименьшего возможного размера.
Для изображений, которые в настоящее время обслуживаются с нашего веб-сайта, общий размер JPEG составляет 4,9 МБ, а общий размер WebP - 3,8 МБ - сокращение на 20% из-за отсутствия реальной работы вполне приветствуется.
Не блокируйте <head>
Вышеупомянутые методы привели нас к очень быстрому статическому веб-сайту. Однако мы хотим быть максимально быстрым сайтом, а это означает решение еще одной проблемы: нам нужно убедиться, что браузер как можно быстрее отображает разметку, которую он извлекает. Традиционно веб-сайты записывали весь свой CSS в отдельный файл и включали его на страницу с тегом <link>
. В эпоху, когда не существовало систем сборки для Интернета, это было простое решение, и оно было настолько хорошим, насколько возможно. К сожалению, браузеры не начнут отображать разметку, даже если они уже завершили выборку HTML-страницы, пока все <link>
таблицы стилей не будут извлечены и обработаны. Если таблица стилей указана в <link>
, браузер должен сделать дополнительный запрос для получения содержимого перед его обработкой. Современные серверы используют HTTP / 2 при обслуживании контента, поэтому стоимость дополнительного запроса не так велика, как раньше, но все же есть небольшая задержка на двусторонний обмен. Хотя для этого можно использовать HTTP / 2 push, он, как правило, сложен в настройке и плохо поддерживается решениями для обслуживания статических файлов.
Вместо того, чтобы указывать отдельный файл CSS в теге <link>
, разработчики, заинтересованные в скорости, обычно рекомендуют встраивать все стили, необходимые для страницы, в теги <style>
непосредственно в HTML. Это может показаться нелепым, так как сайту может потребоваться много CSS, а встраивание всего этого сильно замедлит загрузку начальной HTML-страницы. Действительно, если бы весь CSS для сайта был встроен в страницу, он был бы слишком большим, поэтому только «встраивание всего стиля, необходимого для страницы», является ключом к этому подходу.
Для стилизации веб-сайта мы используем стилизованные компоненты, чтобы объявить CSS прямо в Javascript. Хотя есть две стороны в споре о том, является ли CSS-in-JS хорошим подходом или нет, мы считаем, что удобнее иметь функциональность полного языка программирования, доступную при выполнении сложных задач в CSS. Кроме того, styled-components отлично поддерживает SSR. На этапе предварительной визуализации только стили, необходимые для отображаемого URL-адреса, генерируются и встраиваются в страницу. Это сохраняет содержимое элемента <style>
относительно небольшим, а теперь, когда все стили и разметка хранятся в одном файле, браузеры могут начать отрисовку страницы как можно быстрее. Как только он начнет читать элемент <style>
, браузер начнет синтаксический анализ и обработку включенного CSS, так что, как только он обнаружит разметку в теге <body>
, он сможет отобразить его в режиме реального времени.
Обслуживание
Обслуживать статический сайт очень просто при использовании услуги хостинга статических сайтов. Мы счастливы, что пользуемся Google Cloud Platform для наших общих потребностей, поэтому мы решили использовать веб-хостинг Google Firebase для размещения нашего сайта. Firebase использует Fastly CDN, что позволяет очень быстро обслуживать статические страницы с использованием HTTP / 2 и локальных серверов кеширования. Время первой загрузки страницы может быть таким же быстрым, как и обслуживающая инфраструктура для нее, а с Firebase мы увидели время загрузки нашей начальной HTML-страницы в диапазоне ~ 6–30 мс. Как описано выше, это все время, необходимое браузеру для загрузки всей информации, необходимой для отображения веб-сайта, поэтому очень важно сочетать хороший CDN с этими методами оптимизации.
Помимо обслуживания рабочего сайта из Firebase, мы также обслуживаем версию для разработки из Google App Engine. Для этого подходит App Engine, так как он интегрирован с Cloud Identity-Aware-Proxy от Google, что делает тривиальным ограничение сайта так, чтобы он был виден только членам нашего домена GSuite. Сайт автоматически развертывается в App Engine при каждой фиксации, что критично для этого подхода, поскольку он позволяет предварительно просмотреть контент перед его развертыванием, чтобы убедиться, что он выглядит нормально и, что более важно, просто работает. Хотя изначально для не инженеров немного пугает загружать непосредственно в кодовую базу для добавления контента, этот предварительный просмотр в режиме разработки снимает большую часть этого беспокойства, поскольку все, что работает в App Engine, также будет работать, когда оно отправлено в производственную среду на Firebase. Это не WYSIWYG в реальном времени, как Wordpress, но он довольно близок. :)
Публикация изменений на сайте осуществляется путем создания новой версии в GitHub - это также легко сделать в пользовательском интерфейсе и не проблема для не инженеров. Google Container Builder настроен для автоматической сборки сайта и отправки его в App Engine при основных фиксациях, а также для отправки его в Firebase при создании тега выпуска.
Заключение
Благодаря описанным выше методам мы смогли создать сайт, который (1) включает в себя все важные стили и разметку при начальной загрузке HTML, (2) по-прежнему может использоваться как высокочувствительное одностраничное приложение и (3) автоматически развертывается как для разработки, так и для производства с использованием инструментов Google Cloud Platform. Добавление динамического контента, хотя и не такое простое, как в Wordpress, по-прежнему относительно несложно, а отказ от полностью динамической природы CMS позволяет нам легко обслуживать сайт на малой скорости.