В последние несколько лет генерация статических сайтов и концепции Jamstack эволюционировали от нишевых инструментов к основным подходам к разработке.
Преимущества привлекательны:
- более простое развёртывание и статический хостинг
- лучшая безопасность; есть несколько серверных систем, которые можно использовать
- простое резервное копирование и контроль версий документов с помощью Git
- большой опыт разработки
- сверхбыстрая производительность.
К сожалению, проекты генерации статических сайтов (SSG) редко передаются клиентам. Такие SSG, как Jekyll, Hugo и Gatsby, созданы для разработчиков. Навигация по ветвям версий, обновление документов Markdown и запуск процессов сборки из командной строки разочаровывают редакторов, пришедших из мира публикации в один клик в системе управления контентом.
В этом руководстве описывается один способ сделать всех счастливыми и мотивированными!
- редакторы контента могут использовать WordPress для редактирования и предварительного просмотра сообщений
- разработчики могут импортировать этот контент в Eleventy для создания статического сайта.
Безголовые CMS и слабосвязанные API
Некоторые проиллюстрированные здесь концепции окутаны непонятным жаргоном и терминологией. Я постараюсь избежать этого, но полезно понять общий подход.
Большинство систем управления контентом (CMS) обеспечивают:
- Панель управления контентом для управления страницами, сообщениями, медиа, категориями, тегами и т.д.
- Системы генерации веб-страниц для вставки контента в шаблоны. Обычно это происходит по запросу, когда пользователь запрашивает страницу.
У этого есть некоторые недостатки:
- Сайты могут быть ограничены возможностями CMS и её плагинов.
- Контент часто хранится в HTML, поэтому повторное использование затруднено, например, использование того же контента в мобильном приложении.
- Процесс рендеринга страницы может быть медленным. CMS обычно предлагают варианты кэширования для повышения производительности, но целые сайты могут исчезнуть при сбое базы данных.
- Перейти на альтернативную / лучшую CMS непросто.
Чтобы обеспечить дополнительную гибкость, автономная CMS имеет панель управления контентом, но вместо шаблонов страниц к данным можно получить доступ через API. Тогда любое количество систем может использовать один и тот же контент. Например:
- SSG может получить весь контент во время сборки и отобразить весь сайт
- другой SSG может создать сайт другим способом — например, с премиальным контентом
- мобильное приложение может получать контент по запросу, чтобы показать последние обновления.
Решения Headless CMS включают Sanity.io и Contentful. Они эффективны, но требуют от редакторов изучения новой системы управления контентом.
API REST WordPress
Почти 40% всех сайтов используют WordPress (включая SitePoint.com ). Большинство редакторов контента сталкивались с CMS, и многие будут использовать её ежедневно.
WordPress предоставляет REST API с момента выпуска версии 4.7 в 2016 году. API позволяет разработчикам получать доступ и обновлять любые данные, хранящиеся в CMS. Например, чтобы получить десять последних сообщений, вы можете отправить запрос по адресу:
yoursite.com/wp-json/wp/v2/posts?orderby=date&order=desc
Примечание: этот URL-адрес REST будет работать, только если в настройках WordPress установлены довольно постоянные ссылки, такие как имя сообщения. Если сайт использует URL-адреса по умолчанию, конечной точкой REST будет<yoursite.com/?rest_route=wp/v2/posts?orderby=date&order=desc>.
Это возвращает содержимое JSON, содержащее массив больших объектов для каждого сообщения:
[ { "id": 33, "date": "2020-12-31T13:03:21", "date_gmt": "2020-12-31T13:03:21", "guid": { "rendered": "https://mysite/?p=33" }, "modified": "2020-12-31T13:03:21", "modified_gmt": "2020-12-31T13:03:21", "slug": "my-post", "status": "publish", "type": "post", "link": "https://mysite/my-post/", "title": { "rendered": "First post" }, "content": { "rendered": "<p>My first post. Nothing much to see here.</p>", "protected": false }, "excerpt": { "rendered": "<p>My first post</p>", "protected": false }, "author": 1, "featured_media": 0, "comment_status": "closed", "ping_status": "", "sticky": false, "template": "", "format": "standard", "meta": [], "categories": [1], "tags": [] } ]
По умолчанию WordPress возвращает десять сообщений. Заголовок HTTP x-wp-totalвозвращает общее количество сообщений и x-wp-totalpagesобщее количество страниц.
Примечание: для чтения общедоступных данных аутентификация WordPress не требуется, потому что… они общедоступны! Аутентификация необходима только при попытке добавить или изменить контент.
Таким образом, можно использовать WordPress в качестве автономной CMS и импортировать данные страницы в генератор статических сайтов, например Eleventy. Ваши редакторы могут продолжать использовать известный им инструмент независимо от процессов, которые вы используете для публикации на сайте.
Предупреждения WordPress
В разделах ниже описывается, как импортировать сообщения WordPress на сайт, созданный Eleventy.
В идеальном мире ваш шаблон WordPress и тема Eleventy были бы похожи, поэтому предварительный просмотр страниц отображался бы идентично окончательному сайту. Это может быть сложно: WordPress REST API выводит HTML, и этот код может быть значительно изменён плагинами и темами. Карусель, товар из магазина или контактная форма могут оказаться на вашем статическом сайте, но не работать из-за отсутствия ресурсов на стороне клиента или запросов Ajax к API на стороне сервера.
Мой совет: чем проще настроить WordPress, тем проще будет использовать его в качестве автономной CMS. К сожалению, эти 57 основных плагинов, установленных вашим клиентом, могут вызвать некоторые проблемы.
Установить WordPress
В приведённом ниже демонстрационном коде предполагается, что на вашем компьютере работает WordPress по адресу http: // localhost: 8001 /. Вы можете установить Apache, PHP, MySQL и WordPress вручную, использовать универсальный установщик, такой как XAMPP, или даже получить доступ к живому серверу.
Кроме того, вы можете использовать Docker для управления установкой и настройкой. Создайте новый каталог, например wpheadless, содержащий docker-compose.yml файл:
version: '3' services: mysql: image: mysql:5 container_name: mysql environment: - MYSQL_DATABASE=wpdb - MYSQL_USER=wpuser - MYSQL_PASSWORD=wpsecret - MYSQL_ROOT_PASSWORD=mysecret volumes: - wpdata:/var/lib/mysql ports: - "3306:3306" networks: - wpnet restart: on-failure wordpress: image: wordpress container_name: wordpress depends_on: - mysql environment: - WORDPRESS_DB_HOST=mysql - WORDPRESS_DB_NAME=wpdb - WORDPRESS_DB_USER=wpuser - WORDPRESS_DB_PASSWORD=wpsecret volumes: - wpfiles:/var/www/html - ./wp-content:/var/www/html/wp-content ports: - "8001:80" networks: - wpnet restart: on-failure volumes: wpdata: wpfiles: networks: wpnet:
Запустите docker-compose upиз своего терминала, чтобы запустить WordPress. При первом запуске это может занять несколько минут, так как все зависимости должны быть загружены и инициализированы.
На wp-contentхосте будет создан новый подкаталог, содержащий установленные темы и плагины. Если вы используете Linux, macOS или Windows WSL2, вы можете обнаружить, что этот каталог был создан rootпользователем. Вы можете запустить, sudo chmod 777 -R wp-contentчтобы предоставить права чтения и записи всем пользователям, чтобы вы и WordPress могли управлять файлами.
Примечание: chmod 777не идеально. sudo chown -R www-data: wp-contentДалее следует чуть более безопасный вариант sudo chmod 774 -R wp-content. Это даёт права на запись Apache и всем участникам вашей группы.
Перейдите по адресу http: // localhost: 8001 / в своём браузере и следуйте процессу установки WordPress:
При необходимости измените настройки своего сайта, не забывая установить красивые постоянные ссылки, такие как название сообщения, в меню » Настройки» > » Постоянные ссылки». Затем добавьте или импортируйте несколько сообщений, чтобы у вас были данные для тестирования в Eleventy.
Оставьте WordPress работающим, но, как только вы будете готовы всё выключить, запустите его docker-compose downиз каталога проекта.
Установить Eleventy
Eleventy — популярный генератор статических сайтов на Node.js. В руководстве «Приступая к работе с Eleventy» описывается полная установка, но в приведённых ниже инструкциях показаны основные шаги.
Убедитесь, что у вас установлен Node.js версии 8.0 или выше, затем создайте каталог проекта и инициализируйте package.json файл:
mkdir wp11ty cd wp11ty npm init
Установите Eleventy и Fetch-совместимую библиотеку node-fetch в качестве зависимостей разработки:
npm install @11ty/eleventy node-fetch --save-dev
Затем создайте новый.eleventy.jsфайл конфигурации, который устанавливает подкаталоги source ( /content) и build ( /build):
// .eleventy.js configuration module.exports = config => { return { dir: { input: 'content', output: `build` } }; };
Получение данных сообщений WordPress
Eleventy может получать данные откуда угодно. Файлы JavaScript, содержащиеся в _dataкаталоге содержимого, выполняются автоматически, и любые данные, возвращаемые экспортированной функцией, доступны в шаблонах страниц.
Создайте content/_data/posts.jsфайл в каталоге проекта. Начните с определения конечной точки API сообщений WordPress по умолчанию и node_fetch модуля:
// fetch WordPress posts const wordpressAPI = 'http://localhost:8001/wp-json/wp/v2/posts?orderby=date&order=desc', fetch = require('node-fetch');
За этим следует wpPostPages()функция, которая определяет, сколько вызовов REST необходимо выполнить для получения всех сообщений. Он вызывает URL-адрес WordPress API, но добавляет &_fields=idтолько идентификаторы сообщений — минимально необходимые данные.
Затем x-wp-totalpagesможно проверить заголовок, чтобы получить количество страниц:
// fetch number of WordPress post pages async function wpPostPages() { try { const res = await fetch(`${ wordpressAPI }&_fields=id&page=1`); return res.headers.get('x-wp-totalpages') || 0; } catch(err) { console.log(`WordPress API call failed: ${err}`); return 0; } }
wpPosts() Функция извлекает один набор ( стр ) постов, где каждый имеет свой идентификатор, пули, дата, название, выдержку и содержание возвращены. Строка анализируется в JSON, затем удаляются все пустые и защищённые паролем сообщения (где content.protectedустановлено true).
Примечание: по умолчанию черновики и личные сообщения WordPress. Которые могут просматривать только редакторы контента, не возвращаются /wp-json/wp/v2/postsконечной точкой.
Содержимое сообщения отформатировано для создания дат и чистых строк. В этом примере из полностью определённых URL-адресов WordPress удалён домен http: // localhost: 8001, чтобы гарантировать, что они указывают на отображаемый сайт. При необходимости вы можете добавить дополнительные модификации:
// fetch list of WordPress posts async function wpPosts(page = 1) { try { const res = await fetch(`${ wordpressAPI }&_fields=id,slug,date,title,excerpt,content&page=${ page }`), json = await res.json(); // return formatted data return json .filter(p => p.content.rendered && !p.content.protected) .map(p => { return { slug: p.slug, date: new Date(p.date), dateYMD: dateYMD(p.date), dateFriendly: dateFriendly(p.date), title: p.title.rendered, excerpt: wpStringClean(p.excerpt.rendered), content: wpStringClean(p.content.rendered) }; }); } catch (err) { console.log(`WordPress API call failed: ${err}`); return null; } } // pad date digits function pad(v = '', len = 2, chr = '0') { return String(v).padStart(len, chr); } // format date as YYYY-MM-DD function dateYMD(d) { d = new Date(d); return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()); } // format friendly date function dateFriendly(d) { const toMonth = new Intl.DateTimeFormat('en', { month: 'long' }); d = new Date(d); return d.getDate() + ' ' + toMonth.format(d) + ', ' + d.getFullYear(); } // clean WordPress strings function wpStringClean(str) { return str .replace(/http:\/\/localhost:8001/ig, '') .trim(); }
Наконец, одна экспортированная функция возвращает массив всех отформатированных сообщений. Он вызывает wpPostPages()определение количества страниц, а затем запускается wpPosts()одновременно для каждой страницы:
// process WordPress posts module.exports = async function() { const posts = []; // get number of pages const wpPages = await wpPostPages(); if (!wpPages) return posts; // fetch all pages of posts const wpList = []; for (let w = 1; w <= wpPages; w++) { wpList.push( wpPosts(w) ); } const all = await Promise.all( wpList ); return all.flat(); };
Возвращённый массив пост-объектов будет выглядеть примерно так:
[ { slug: 'post-one', date: new Date('2021-01-04'), dateYMD: '2021-01-04', dateFriendly: '4 January 2021', title: 'My first post', excerpt: '<p>The first post on this site.</p>', content: '<p>This is the content of the first post on this site.</p>' } ]
Рендеринг всех постов в Eleventy
Функция разбивки на страницы Eleventy может отображать страницы из сгенерированных данных. Создайте content/post/post.njk файл шаблона Nunjucks со следующим кодом для извлечения posts.jsданных ( posts) и вывода каждого элемента (’post’) в каталог, названный в соответствии с названием сообщения:
--- pagination: data: posts alias: post size: 1 permalink: "/{{ post.slug | slug }}/index.html" --- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{ post.title }}</title> <meta name="viewport" content="width=device-width,initial-scale=1" /> <style> body { font-size: 100%; font-family: sans-serif; } </style> </head> <body> <h1>{{ post.title }}</h1> <p><time datetime="{{ post.dateYMD }}">{{ post.dateFriendly }}</time></p> {{ post.content | safe }} </body> </html>
Запустите npx eleventy —serveиз терминала в корневом каталоге проекта, чтобы сгенерировать все сообщения и запустить сервер разработки.
Если сообщение со слагом post-oneбыло создано в WordPress, вы можете получить к нему доступ на своём новом сайте Eleventy по адресу http: // localhost: 8080 / post-one / :
Создание индексных страниц
Чтобы упростить навигацию, можно создать аналогичную страницу с разбивкой на страницы content/index.njk. Это отображает пять элементов на странице со ссылками на «новые» и «старые» публикации:
--- title: WordPress articles pagination: data: posts alias: pagedlist size: 5 --- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{ post.title }}</title> <meta name="viewport" content="width=device-width,initial-scale=1" /> <style> body { font-size: 100%; font-family: sans-serif; } ul, li { margin: 0; padding: 0; } ul { list-style-type: none; display: flex; flex-wrap: wrap; gap: 2em; } li { flex: 1 1 15em; } li.next { text-align: right; } a { text-decoration: none; } a h2 { text-decoration: underline; } </style> </head> <body> <h1> {% if title %}{{ title }}{% else %}{{ list }} list{% endif %} {% if pagination.pages.length > 1 %}, page {{ pagination.pageNumber + 1 }} of {{ pagination.pages.length }}{% endif %} </h1> <ul class="posts"> {%- for post in pagedlist -%} <li> <a href="/{{ post.slug }}/"> <h2>{{ post.title }}</h2> <p><time datetime="{{ post.dateYMD }}">{{ post.dateFriendly }}</time></p> <p>{{ post.excerpt | safe }} </a> </li> {%- endfor -%} </ul> <hr> {% if pagination.href.previous or pagination.href.next %} <ul class="pages"> {% if pagination.href.previous %} <li><a href="{{ pagination.href.previous }}">« newer posts</a></li> {% endif %} {% if pagination.href.next %} <li class="next"><a href="{{ pagination.href.next }}">older posts »</a></li> {% endif %} </ul> {% endif %} </body> </html>
Выполненный npx eleventy —serveвами выше должен оставаться активным, но при необходимости запустите его снова. В index.htmlкаталоге создаётся файл build, который ссылается на первые пять сообщений. Дополнительные страницы содержатся в build/1/index.htmlи build/2/index.html т.д.
Перейдите по адресу http: // localhost: 8080 /, чтобы просмотреть индекс:
Пресса Ctrl| Cmd+ Cдля выхода с сервера Eleventy. Выполните npx eleventyсамостоятельно, чтобы создать полный сайт, готовый к развертыванию.
Решения о развёртывании
В результате ваш сайт Eleventy содержит статический HTML и ресурсы. Его можно разместить на любом веб-сервере без серверных сред выполнения, баз данных или других зависимостей.
Вашему сайту WordPress требуются PHP и MySQL, но он может быть размещён где угодно для редакторов контента. Сервер в сети частной компании является наиболее безопасным вариантом. Но вам может потребоваться общедоступный веб-сервер для удалённых сотрудников. Любой из них можно защитить с помощью ограничений IP-адреса, дополнительной аутентификации и т.д.
Ваши сайты Eleventy и WordPress могут размещаться на разных серверах, возможно, доступ к ним осуществляется из разных поддоменов, таких как www.mysite.comи editor.mysite.com соответственно. Ни один из них не будет конфликтовать с другим, и будет легче управлять всплесками трафика.
Однако вы можете предпочесть оставить оба сайта на одном сервере, если:
- у вас есть статические страницы (услуги, информация, контакты и т. д.) и некоторые страницы WordPress (магазин, форумы и т. д.), или
- статический сайт получает доступ к данным WordPress, таким как загруженные изображения или другие API REST.
PHP инициирует рендеринг WordPress только в том случае, если URL-адрес не может быть разрешён другим способом. Например, предположим, что у вас есть сообщение WordPress с постоянной ссылкой /post/my-article. Он будет обслуживаться при доступе пользователя, mysite.com/post/my-article если только статический файл с именем /post/my-article/index.htmlне был сгенерирован Eleventy в корневом каталоге сервера.
К сожалению, редакторы контента не смогут просматривать статьи из WordPress, поэтому вы можете рассмотреть возможность условной перезаписи URL. Эта.htaccessконфигурация Apache загружает все /post/URL-адреса из соответствующего /static/каталога, за исключением случаев, когда IP-адрес пользователя 1.2.3.4:
RewriteEngine OnRewriteCond %{REMOTE_HOST} !^1.2.3.4RewriteRule «^/post/(.*)» «/static/post/$1/index.html» [R]
Для более сложных сайтов вы можете использовать Eleventy для рендеринга файлов конфигурации сервера на основе созданных вами страниц.
Наконец, вы можете захотеть представить процесс, который автоматически запускает процесс сборки и развёртывания Eleventy. Вы могли бы рассмотреть:
- Сборка каждые N часов независимо от изменений.
- Предоставьте большую кнопку «РАЗВЕРТИТЬ» для редакторов контента. Это может быть интегрировано в панели администрирования WordPress.
- Используйте WordPress REST API, чтобы часто проверять modifiedдату самой последней публикации. Перестройку можно начать, когда она позже даты последней сборки.
Более простые статические сайты
Этот пример иллюстрирует основы использования WordPress в качестве автономной CMS для создания статических сайтов. Это обнадёживает просто, хотя для более сложного содержимого потребуется более сложный код.
Предложения по дальнейшему улучшению:
- Попробуйте импортировать сообщения с существующего сайта WordPress, который использует сторонние темы и плагины.
- Измените возвращённый HTML, чтобы удалить или адаптировать виджеты WordPress.
- Импортируйте дополнительные данные, такие как страницы, категории и теги ( комментарии также возможны, хотя и менее полезны).
- Извлеките изображения или другие носители в локальную файловую систему.
- Подумайте, как вы можете кэшировать записи WordPress в локальном файле для более быстрой визуализации. Можно проверить _fields=modifiedдату, чтобы убедиться, что новые и обновлённые сообщения импортируются.
WordPress вряд ли будет лучшим вариантом для управления статическим контентом сайта. Однако, если вы уже используете WordPress и думаете о переходе на генератор статических сайтов, его REST API предоставляет возможный путь миграции без полного отказа от CMS. от CMS.