Статические веб-сайты содержат страницы с неизменным содержимым. Технически — это набор HTML-файлов, которые, для каждого посетителя сайта, выглядят одинаково. В отличие от динамических веб-сайтов, для разработки таких сайтов не нужно серверное программирование или базы данных. Публикация статического веб-сайта проста: файлы выгружают на обыкновенный веб-сервер или в некое хранилище. Два основных преимущества статических веб-сайтов — это безопасность и скорость. Тут нет базы данных, поэтому отсюда нечего красть, и тут нет нужды программно генерировать страницу для каждого запроса, что ускоряет работу.

Для того чтобы упростить создание статических веб-сайтов, создано множество опенсорсных инструментов. Например, это Jekyll, Hugo, Hexo, и другие. Работа по подготовке содержимого сайта ведётся путём редактирования чего-то вроде файлов с разметкой, или через некое API для управления контентом. После того, как данные готовы к публикации, генератор берёт эти данные, внедряет их в шаблоны и создаёт множество HTML-файлов, готовых для публикации.

Сегодня мы расскажем о быстрой разработке проектов с помощью Gatsby — генератора статических прогрессивных веб-приложений, и Strapi — системы управления контентом. В результате после того, как вы освоите это руководство, у вас будет работающий статический блог и масса идей, касающихся его развития.

Статические веб-сайты и прогрессивные веб-приложения


Полагаем, прежде чем продолжать, стоит сказать пару слов о прогрессивных веб-приложениях (Progressive Web Apps, PWA). Это — веб-приложения, интенсивно использующие JavaScript, которые отличаются надёжностью, быстротой и привлекательным внешним видом. PWA, благодаря обеспечиваемой ими скорости работы со страницами, и тому, что пользователям удобно с ними взаимодействовать, стали стандартным способом построения веб-интерфейсов. В результате появилось множество фронтенд-фреймворков, таких, как Angular, Vue, и React.

Мы, зная о плюсах статических веб-сайтов и PWA, задались идеей найти способ использовать одновременно и то и другое. Инструмент, который это позволяет, называется Gatsby.

Что такое Gatsby?


Gatsby — это невероятно быстрый фреймворк для разработки веб-сайтов на React. Он позволяет создавать сайты, основанные на React буквально за считанные минуты. Gatsby подходит для проектов разных масштабов — от блогов до корпоративных веб-сайтов.

Так как проекты, созданные с помощью Gatsby, основаны на React, их страницы, при взаимодействии с ними пользователя, не перезагружаются, что делает такие проекты очень быстрыми. Gatsby поддерживает большой набор плагинов, которые позволяют, в частности, использовать данные из различных источников (это, например, markdown-файлы, различные системы управления контентом, и так далее). Центром системы данных Gatsby является интерфейс «узлов», которые используются для моделирования данных, поступающих в Gatsby.

Этот проект создал Кайл Мэтьюз, он официально выпущен в июле 2017-го и уже используется десятками компаний.

Как уже стало понятно, Gatsby, для того, чтобы он мог генерировать HTML-файлы, нужно откуда-то брать данные для них. В нашем случае источником данных будет Strapi.

Что такое Strapi?


Strapi — это фреймворк для управления контентом, основанный на Node.js. Он позволяет быстро разрабатывать API для работы с данными и занимает промежуточное положение между фреймворком для Node.js и CMS без пользовательского интерфейса. Strapi позволяет разрабатывать API очень быстро, что экономит время.


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

Strapi — это опенсорсный проект, что выгодно отличает его от других CMS. В частности, это означает, что, во-первых, он полностью бесплатен, а во-вторых — то, что компания, выбравшая Strapi, разворачивает CMS на собственных серверах, то есть данные компании остаются под её полным контролем. Кроме того, Strapi можно настраивать и расширять благодаря системе плагинов.

Описав наши инструменты, приступим к разработке статического блога.

Настройка API с помощью Strapi


Воспользуемся возможностями Strapi для разработки API и добавим в систему данные, которые позже превратятся в страницы блога.

?Установка Strapi


Для работы Strapi нужны Node 8 (или выше) и MongoDB. Если в вашей системе всё это имеется, можно приступать к установке Strapi с использованием npm:

$ npm i strapi@alpha -g

Обратите внимание на то, что Strapi v3 всё ещё находится на стадии альфа-версии, но нам это подойдёт.

?Создание проекта Strapi


Создадим директорию gatsby-strapi-tutorial:

$ mkdir gatsby-strapi-tutorial

Создадим каркас API внутри этой директории, для чего сначала перейдём в эту директорию, а потом выполним там соответствующую команду Strapi:

$ cd gatsby-strapi-tutorial
$ strapi new api

?Запуск сервера


Для запуска сервера Strapi сначала перейдём в соответствующую подпапку директории проекта:

$ cd api

Затем запустим сервер, основанный на Node.js:

$ strapi start

Теперь, если всё сделано правильно, можно будет посетить панель администрирования проекта, которая расположена по адресу http://localhost:1337/admin.

?Создание первого пользователя


Добавим первого пользователя со страницы регистрации.


?Создание типа контента


API Strapi основано на структурах данных, которые называются типами контента (Content Type). Это — эквивалент моделей во фреймворках и типов контента в WordPress.

Создадим тип контента с именем article и с тремя полями: title (тип string), content (тип text), и author (тип relation, у одного автора может быть несколько статей).



?Добавление материалов статей


Добавим в базу данных несколько статей. Для того чтобы создать статью, выполните следующую последовательность действий:

  1. Посетите страницу списка статей.
  2. Щёлкните Add New Article.
  3. Введите данные, ссылку на автора, и отправьте форму.

Создайте, пользуясь тем же подходом, ещё две статьи.


?Настройка доступа


Из соображений безопасности доступ к API, по умолчанию, ограничен. Для того чтобы открыть доступ, посетите раздел аутентификации и разрешений для роли Guest, выберите действие Article - find и сохраните изменения. С этого момента у вас должна появиться возможность запрашивать список статей.

Доступ к API автора так же ограничен. Разрешите анонимный доступ, выбрав действие find (в разделе Users & Permissions) и сохранив изменения.


Разработка статического веб-сайта


Теперь, когда API готово, можно приступать к разработке статического веб-сайта.

?Установка Gatsby и создание проекта


Установим Gatsby CLI следующей командой:

$ npm install --global gatsby-cli

В ранее созданной папке gatsby-strapi-tutorial создадим новый блог:

$ gatsby new blog

?Запуск сервера Gatsby в режиме разработки


Перейдём в папку проекта:

$ cd blog

Запустим сервер:

$ gatsby develop

После этого посмотреть веб-сайт, созданный Gatsby, можно будет, перейдя по адресу http://localhost:8000/.

?Установка плагина для работы с источником данных Strapi


Данные, которые являются основой статического сайта, могут поступать из разных источников. Например, это markdown-файлы, CSV-файлы, материалы из WordPress (работа с которыми возможна благодаря плагину JSON REST API), и так далее.

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

Для того, чтобы подключить Gatsby к источнику данных, требуется плагин, предназначенный для работы с этим источником данных. Такой плагин можно разработать самостоятельно, либо подобрать из уже существующих. В этом примере мы, в качестве источника данных, будем использовать Strapi, а значит, нам понадобится плагин источника данных для API, построенных с помощью Strapi. Мы уже разработали такой плагин.

Установим его:

$ npm install --save gatsby-source-strapi

Этот плагин нуждается в некоторой настройке. Замените содержимое файла gatsby-config.js на следующее:

module.exports = {  
  siteMetadata: {
    title: `Gatsby Default Starter`,
  },
  plugins: [
    `gatsby-plugin-react-helmet`,
    {
      resolve: `gatsby-source-strapi`,
      options: {
        apiURL: `http://localhost:1337`,
        contentTypes: [ // Список типов контента, которые планируется запрашивать из Gatsby.
          `article`,
          `user`
        ]
      },
    },
  ],
}

Теперь перезапустите сервер для того, чтобы позволить Gatsby воспринять изменения.

?Список статей


Для начала нам хочется вывести список статей. Для того чтобы это сделать, добавьте следующее содержимое в существующий файл домашней страницы src/pages/index.js:

import React from 'react'  
import Link from 'gatsby-link'
const IndexPage = ({ data }) => (  
  <div>
    <h1>Hi people</h1>
    <p>Welcome to your new Gatsby site.</p>
    <p>Now go build something great.</p>
    <ul>
      {data.allStrapiArticle.edges.map(document => (
        <li key={document.node.id}>
          <h2><font color="#3AC1EF">
            <Link to={`/${document.node.id}`}>{document.node.title}</Link>
          </font></h2>
          <p>{document.node.content}</p>
        </li>
      ))}
    </ul>
    <Link to="/page-2/">Go to page 2</Link>
  </div>
)
export default IndexPage
export const pageQuery = graphql`  
  query IndexQuery {
    allStrapiArticle {
      edges {
        node {
          id
          title
          content
        }
      }
    }
  }
`

В конце файла мы экспортируем pageQuery — запрос GraphQL, который позволяет получить полный список статей. Как видите, нам нужны лишь поля id, title и content, и GraphQL позволяет получить именно то, что нам нужно.

Затем мы передаём деструктурированный объект { data } как параметр IndexPage и, для получения выводимых данных, перебираем его поле allStrapiArticles.


Для того, чтобы упростить разработку запросов GraphQL, можно воспользоваться соответствующим интерфейсом Gatsby. Взгляните на него и попробуйте создать несколько запросов.

?Страница просмотра статьи


Теперь то, что мы делаем, уже похоже на блог, и это хорошо. Однако одной важной вещи всё ещё не хватает: страницы просмотра статьи.

Создадим шаблон, содержащий запрос GraphQL и зададим отображаемое содержимое. Делать это будем в файле src/templates/article.js:

import React from 'react'  
import Link from 'gatsby-link'
const ArticleTemplate = ({ data }) => (  
  <div>
    <h1>{data.strapiArticle.title}</h1>
    <p>by <Link to={`/authors/${data.strapiArticle.author.id}`}>{data.strapiArticle.author.username}</Link></p>
    <p>{data.strapiArticle.content}</p>
  </div>
)
export default ArticleTemplate
export const query = graphql`  
  query ArticleTemplate($id: String!) {
    strapiArticle(id: {eq: $id}) {
      title
      content
      author {
        id
        username
      }
    }
  }
`

Смотрится хорошо, но в данный момент Gatsby не знает, когда нужно выводить этот шаблон. Для каждой статьи нужен собственный URL. Сообщим Gatsby о новых URL с помощью функции createPage.

Для того чтобы это сделать, создадим новую функцию, которая называется makeRequest и используется для выполнения запроса GraphQL. Затем мы экспортируем функцию, которая называется createPage, в которой мы получаем список статей и создаём страницу для каждой из них. Делается это всё в файле gatsby-node.js. Вот что у нас получилось:

const path = require(`path`);
const makeRequest = (graphql, request) => new Promise((resolve, reject) => {  
  // Запрос для получения данных, используемых при создании страниц.
  resolve(
    graphql(request).then(result => {
      if (result.errors) {
        reject(result.errors)
      }
      return result;
    })
  )
});
// Реализация функции Gatsby API "createPages". Она вызывается один раз когда 
// уровень данных готовится к работе для того, чтобы позволить плагину создать из этих данных страницы
exports.createPages = ({ boundActionCreators, graphql }) => {  
  const { createPage } = boundActionCreators;
  const getArticles = makeRequest(graphql, `
    {
      allStrapiArticle {
        edges {
          node {
            id
          }
        }
      }
    }
    `).then(result => {
    // Создаём страницы для каждой статьи
    result.data.allStrapiArticle.edges.forEach(({ node }) => {
      createPage({
        path: `/${node.id}`,
        component: path.resolve(`src/templates/article.js`),
        context: {
          id: node.id,
        },
      })
    })
  });
  // Запрашиваем материалы статей для использования при создании страниц.
  return getArticles;
};

Перезапустите сервер Gatsby. Теперь ссылки на статьи должны оказаться рабочими, щелчки по ним будут открывать страницы статей.


?Страница автора


Статьи пишут авторы. Они заслуживают отдельной страницы. Процесс по созданию страницы автора очень похож на процесс создания страницы статьи. Для начала создадим шаблон в файле src/templates/user.js:

import React from 'react'  
import Link from 'gatsby-link'
const UserTemplate = ({ data }) => (  
  <div>
    <h1>{data.strapiUser.username}</h1>
    <ul>
      {data.strapiUser.articles.map(article => (
        <li key={article.id}>
          <h2><font color="#3AC1EF">
            <Link to={`/${article.id}`}>{article.title}</Link>
          </font></h2>
          <p>{article.content}</p>
        </li>
      ))}
    </ul>
  </div>
)
export default UserTemplate
export const query = graphql`  
  query UserTemplate($id: String!) {
    strapiUser(id: { eq: $id }) {
      id
      username
      articles {
        id
        title
        content
      }
    }
  }
`

Теперь обновим файл gatsby-node.js для создания соответствующих ссылок:

const path = require(`path`);
const makeRequest = (graphql, request) => new Promise((resolve, reject) => {  
  // Запрос для получения данных, используемых при создании страниц.
  resolve(
    graphql(request).then(result => {
      if (result.errors) {
        reject(result.errors)
      }
      return result;
    })
  )
});

// Реализация функции Gatsby API "createPages". Она вызывается один раз когда 
// уровень данных готовится к работе для того, чтобы позволить плагину создать из этих данных страницы
exports.createPages = ({ boundActionCreators, graphql }) => {  
  const { createPage } = boundActionCreators;
  const getArticles = makeRequest(graphql, `
    {
      allStrapiArticle {
        edges {
          node {
            id
          }
        }
      }
    }
    `).then(result => {
    // Создаём страницы для каждой статьи.
    result.data.allStrapiArticle.edges.forEach(({ node }) => {
      createPage({
        path: `/${node.id}`,
        component: path.resolve(`src/templates/article.js`),
        context: {
          id: node.id,
        },
      })
    })
  });
  const getAuthors = makeRequest(graphql, `
    {
      allStrapiUser {
        edges {
          node {
            id
          }
        }
      }
    }
    `).then(result => {
    // Создаём страницы для каждого пользователя.
    result.data.allStrapiUser.edges.forEach(({ node }) => {
      createPage({
        path: `/authors/${node.id}`,
        component: path.resolve(`src/templates/user.js`),
        context: {
          id: node.id,
        },
      })
    })
  });
  // Запросы материалов статей и данных авторов для использования при создании страниц.
  return Promise.all([
    getArticles,
    getAuthors,
  ])
};

И наконец, перезагрузим сервер и посетим страницу автора, перейдя на неё со страницы просмотра статьи по соответствующей ссылке.


Итоги


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

Что делать дальше?

Вы вполне можете продолжить работу над этим проектом для того, чтобы изучить полезные возможности Gatsby и Strapi. Сюда можно добавить список авторов, категории статей, систему комментирования на базе Strapi API или Disqus. Кроме того, вы можете, пользуясь предложенной здесь методикой, создавать и другие веб-сайты (интернет-магазины, корпоративные сайты, и так далее).

Когда ваш проект будет готов к работе, вы, возможно, захотите выложить его в интернет. Веб-проекты, сгенерированные Gatsby, можно публиковать, пользуясь средствами сервисов для размещения статических сайтов, таких, как Netlify, S3/Cloudfront, страницы GitHub, страницы GitLab, Heroku, и так далее. API Strapi — это система, основанная на Node.js, поэтому оно может быть развёрнуто на базе Heroku или с используемой любой виртуальной машины Linux с установленным на ней Node.js.

Полную версию кода, который мы тут разбирали, можно найти на GitHub. Для того, чтобы увидеть то, что тут создано, в действии, клонируйте репозиторий, выполните команду npm run setup, запустите сервер Strapi (перейдите в папку api и выполните команду strapi start) и сервер Gatsby (перейдите в папку blog и выполните команду npm run develop).

Уважаемые читатели! Как вы создаёте статические веб-сайты? Планируете ли вы использовать связку Gatsby-Strapi в своих проектах?

Комментарии (6)


  1. rhamdeew
    01.02.2018 14:28

    Не совсем понятно — получается что в вашем решении для того чтобы сайт работал надо постоянно держать запущенным strapi? Или можно допустим локально запустить strapi, добавить контент, запустить генератор и получившуюся статику залить на сервер?

    Также интересен вариант с комментариями и Strapi API.


    1. farcaller
      01.02.2018 14:34

      gatsby компилит приложение в, условно, статику — потому запросы к strapi будут только когда вы пересобираете gatsby-фронт.


      1. Tauyekel
        02.02.2018 16:09

        То есть, чтобы на сайте появились новые блог посты, нужно каждый раз пересобирать gatsby-фронт?


        1. farcaller
          02.02.2018 17:09

          ну да. В этом плане оно все такое же как jekyll и co


    1. rhamdeew
      01.02.2018 15:23

      Ах да, забыл — сам юзаю hexo и jekyll для статичных блогов.
      Обхожусь без админ-панели. Проблема только с комментариями (не нравится Disqus).


  1. exdeniz
    01.02.2018 15:01

    У strapi в альфе нет плагина media, так что пока это очень-очень alpha. Для простого блога можно взять тот же contentful и не мучатся или markdown