Создаем собственный блог с помощью Next.js и Strapi

В этой статье вы узнаете:

  • о назначении CMS;
  • как использовать Strapi;
  • как с помощью Next.js и Strapi создавать статические сайты.

Введение

Предположим, что вы создаете блог при помощи Next.js. Для добавления в него поста нужно будет сделать следующее:

  • перейти в каталог /pages/article проекта;
  • создать в нем файл 1.js, чтобы Next.js отрисовывал пост при переходе пользователя по маршруту /article/1;
  • в дальнейшем для добавления других постов понадобится создать 2.js, 3.js и т.д.

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

Есть ли способ упростить этот процесс?

Здесь нам поможет CMS (система управления контентом). С помощью нее можно редактировать содержимое сайта без каких-либо затруднений. Прошло то время, когда для внесения простейших правок в сайт приходилось перелопачивать кучу строк кода. Используя CMS, можно изменять или загружать посты всего в несколько кликов.

Одним из известных примеров CMS является Strapi.

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

В общем, если вы хотите упростить управление блогом, то CMS очень пригодится.

Приступим!

CMS

Настройка

Для запуска Strapi выполните в терминале:

Команда для инициализации Strapi

Следующая команда запустит процесс настройки:

Команда для настройки CMS

Вывод:

Вывод в терминале

Как видите, Strapi предлагает настроить проект. Для этого перейдите на страницу localhost:1337/admin и введите учетные данные для запуска CMS.

Ввод регистрационных данных

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

Панель инструментов

С первой страницей мы разобрались. Далее познакомимся с плагином Content-Types.

Content-Types builder

Согласно документации Strapi, это один из важнейших плагинов. Здесь можно создавать коллекции и отдельные типы. Все это мы разберем поочередно. 

В боковой панели кликните по “Content-Types Builder” в категории Plugins.

Сначала мы создадим простой тип коллекции. Для этого кликните “Create new collection type”.

В диалоговом окне пропишите “Display name” как “Article” и кликните “Continue”:

После этого Strapi предложит добавить поля в новый тип коллекции. Создайте поле “Title” и кликните “Finish” для продолжения:

Создание нового поля

Далее кликните “Add another field”, а затем “Rich Text”. Здесь укажите имя “Body”:

Добавление поля Rich Text

Давайте подытожим, что мы сделали к этому моменту:

  • создали тип коллекции “Article”;
  • сообщили Strapi, что в наших статьях будет по два поля;
  • первое поле “Title”, имеющее тип Text;
  • второе поле “Body”, имеющее тип Rich Text.

Это означает, что можно создать коллекцию статей, которые затем будет отрисовывать Next.js.

Осталось только сохранить изменения. Кликните “Save” в верхнем правом углу:

Если все пройдет успешно, в категории Collection Types должен появиться пункт “Articles”.

У нас все получилось!

Все прошло гладко, и теперь у нас есть коллекция “Articles”.

Далее нужно создать отдельный тип. Кликните в боковой панели “Create new single type”.

Создание отдельного типа

Назовите этот тип “About Me” и присвойте ему текстовое поле с именем “Body”:

Создание текстового поля в типе

Кликните “Save”, чтобы завершить внесение изменений. Теперь тип “About Me” должен появиться там же в боковой панели.

На этом этапе мы проделали следующее:

  • сообщили Strapi, что нам нужен отдельный тип с именем “About Me”:
  • этот тип будет содержать всего одно текстовое поле с именем “Body”.

Проще говоря, отдельный тип служит для контента, который не относится к коллекции (например, меню ресторана, страница About Me или футер).

А теперь напишем первую статью.

Написание статей

Кликните в боковой панели “Articles”, а затем “Add New Articles”. Здесь вы можете добавлять новые элементы в коллекцию “Articles”.

Создание статьи

Теперь вы увидите страницу создания записи для коллекции. Внесем сюда какую-нибудь информацию. Поле Rich Text принимает разметку Markdown.

Создание записи

После введения информации в поля кликните “Save”. Когда Strapi сохранит запись, жмите “Publish”.

Публикация статьи

Вывод:

Статья опубликована

Поздравляю! Вы опубликовали свой первый пост!

На этом раздел заканчивается, и мы переходим к типу “About Me”.

Страница About Me

В боковой панели кликните “About Me” и введите в текстовое поле какой-нибудь текст. По завершению нажмите “Save”.

Создание записи в About Me

В завершении опубликуйте созданную запись.

Публикация записи

Вот и все. Далее мы перейдем к извлечению этих данных с помощью запросов GET.

Получение данных

После того, как мы написали посты, нам нужно выполнить простой запрос на получение, чтобы отобразить их во фронтенде. Пока выполним запрос GET из командной строки:

Запрос статей через командную строку

Вот результат:

Ошибка!

Вместо списка постов мы получили ошибку 403. Чтобы от нее избавиться, нужно установить публичный доступ в настройках безопасности Strapi.

Для этого кликните по “Settings” в боковой панели.

Далее в разделе “Users & Permissions Plugin” кликните “Roles”:

В разделе “Roles” выберите “Public”:

После этого промотайте вниз к разделу “Permissions”. В выпадающем меню “Application” отметьте следующие опции:

Отметьте эти опции

Таким образом мы сообщили Strapi, что следующая информация должна быть публичной:

  • содержимое отдельного типа “About Me” (то есть действие find);
  • содержимое и количество элементов, находящихся в коллекции “Articles”. Другими словами, действия count, findone и find являются публичными.

Проверим все это. Для получения списка статей выполните в терминале следующую команду:

Получение статей

Ожидаемый вывод:

Как видите, мы получили массив элементов из коллекции “Articles”.

Далее для получения содержимого типа “About Me” выполните:

Получение данных из “About Me”

Ожидаемый вывод:

Отлично! Все работает, как надо.

На этом очередной раздел закончен. Далее мы займемся разработкой фронтенд-части с помощью Next.js, чтобы отображать статьи на веб-странице.

Фронтенд

Установка

Для инициализации проекта Next.js выполните в терминале:

Инициализация репозитория Next.js

Внутри проекта перейдите в файл /pages/index.js и найдите следующий фрагмент кода:

Код, который нужно найти в /pages/index.js

Удалите код между родительскими тегами div. В итоге /pages/index.js должен выглядеть так:

import Head from "next/head";

export default function Home() {
  return (
    <div>
      <Head>
        <title>Home</title>
      </Head>
      <h1>Welcome home!</h1>
    </div>
  );
}
  • строка 7: устанавливаем заголовок для домашней страницы.

Выполните код. Результат будет следующим:

Вывод кода

Далее мы перейдем к настройке переменных конфигурации.

Переменные конфигурации

Нас интересует переменная url. Она будет указывать на расположение Strapi. В процессе разработки URL-адресом Strapi будет http://localhost:1337/.

В корне проекта создайте каталог config, а в нем файл next.config.js

В этом файле пропишите следующий код:

const production = process.env.NODE_ENV === "production";

export const url = production
  ? "https://www.yoursite.com"
  : "https://localhost:1337";
  • строка 1: логическое значение production будет указывать, находимся ли мы в среде продакшена;
  • строка 3: если да, то переменная url должна иметь значением URL-адрес развернутого экземпляра Strapi. В противном случае она должна указывать на https://localhost:1337/.

Далее мы перейдем к установке необходимых пакетов.

Установка библиотек

Нам потребуется всего один пакет:

  • react-markdown: поскольку посты будут писаться в Markdown, этот пакет займется отрисовкой кода Mardown и его заменой на HTML.

Выполните в терминале:

Установка пакета

Далее займемся отображением списка постов на веб-странице.

Отображение данных

Импортируйте в /pages/index.js созданную ранее переменную url:

Код для внесения в /pages/index.js

Теперь добавьте в конец этого файла следующий код:

export const getStaticProps = async () => {
  const data = await fetch(`${url}/articles`);
  const list = await data.json();

  return {
    props: {
      list,
    },
    revalidate: 1, 
  };
};
  • строка 1: метод getStaticProps дает Next.js команду применить статическую генерацию для получения данных из CMS;
  • строки 2–3: получение списка статей и преобразование этих данных в JSON;
  • строка 6: экспорт списка в виде пропсов.
  • строка 9: свойство revalidate сообщает Next.js, сколько времени нужно ожидать перед повторной генерацией страницы. В данном случае мы будем выполнять повторную генерацию каждую секунду.

Теперь найдите в /pages/index.js следующий код:

Код, который нужно найти в /pages/index.js

Измените его на:

Изменение кода в /pages/index.js

Эта строка позволяет внести пропс list в компонент Home.

Далее найдите в pages/index.js блок return:

Код, который нужно найти в /pages/index.js

Замените его следующим:

return (
    <div>
      <Head>
        <title>Home</title>
      </Head>
      <h1>Welcome home!</h1>
      <ul>
        {list.map((item) => (
          <li key={item.id}>
            <Link href={`/article/${item.id}`}>
              <a>{item.Title}</a>
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
  • строка 8: используем map для получения всех элементов массива list.
  • строка 10: при клике каждый элемент будет указывать на URL /article/$id, где $id является ID элемента массива. Кроме того, на этом шаге реализуется динамическая маршрутизация.
  • строка 11: отображаем только поле Title элементов.

Выполните код. Ожидаемый результат:

Вывод кода

Здорово! Код работает. Дополнительно проверим его, добавив еще один пост. Для этого перейдите обратно в панель Admin и добавьте запись в коллекцию “Article”:

Добавление поста в Strapi

После публикации обновите страницу. Результат должен получиться такой:

Вывод кода

Как видите, теперь можно генерировать новые посты без дополнительного написания кода. 

В итоге файл /pages/index.js должен выглядеть так:

import Head from "next/head";
import { url } from "../config/next.config";
import Link from "next/link";
export default function Home({ list }) {
  console.log(list);
  return (
    <div>
      <Head>
        <title>Home</title>
      </Head>
      <h1>Welcome home!</h1>
      <ul>
        {list.map((item) => (
          <li key={item.id}>
            <Link href={`/article/${item.id}`}>
              <a>{item.Title}</a>
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
}
export const getStaticProps = async () => {
  const data = await fetch(`${url}/articles`);
  const list = await data.json();

  return {
    props: { list },
    revalidate: 1,
  };
};

Просмотр статьи

До этого мы реализовали динамическую маршрутизацию. При нажатии по любому элементу он будет перенаправлять на страницу /article/$id. Осталось только обработать этот URL.

Создайте в директории /pages каталог article, а в нем файл [id].js.

В этом файле пропишите следующий код:

Код для /pages/article/[id].js

Выполните код. Ожидаемый результат:

Вывод кода

Теперь поработаем над извлечением и отображением содержимого постов.

Импортируйте в /pages/article/[id].js переменную url и пакет ReactMarkdown:

Код для /pages/article/[id].js

Далее пропишите в конце файла следующий код:

export const getStaticProps = async (context) => {
  const data = await fetch(`${url}/articles/${context.params.id}`);
  const article = await data.json();

  return {
    props: { article },
    revalidate: 1,
  };
};
export async function getStaticPaths() {
  const res = await fetch(`${url}/articles`);
  const articles = await res.json();

  const paths = articles.map((item) => ({
    params: { id: item.id.toString() },
  }));

  return { paths, fallback: false };
}
  • Строка 1: используем статическую генерацию для получения данных. Переменная context позволяет извлечь параметры, находящиеся в URL.
  • Строки 2–3: получаем данные статьи согласно представленному URL. Например, если пользователь находится на /article/3, выполняем запрос на получение статьи с id равным 3.
  • Строка 6: возвращаем данные статьи в качестве пропсов.
  • Строка 7: просим Next.js выполнять инкрементную генерацию сайта каждую секунду.
  • Строка 9: используем getStaticPaths для статической генерации путей.
  • Строка 13: сохраняем все поля id каждого элемента в отдельном массиве.
  • Строка 17: возвращаем список всех путей, которые Next.js нужно сгенерировать. Свойство fallback говорит Next.js отображать ошибку 404, если пользователь переходит по несуществующему пути.

После экспорта всех данных статьи нужно их отобразить. Найдите в /pages/article/[id].js следующий фрагмент кода:

Код, который нужно найти в /pages/article/[id].js

Измените его так:

Код для замены в /pages/article/[id].js

Далее найдите блок return:

Код, который нужно найти в /pages/article/[id].js

Замените его на:

Код для замены в /pages/article/[id].js
  • Строка 3: отображаем Title в элементе заголовка.
  • Строка 4: отображаем тело с помощью компонента ReactMarkdown. Он будет отрисовывать код Markdown и преобразовывать его в HTML.

Выполните код. Ожидаемый результат:

Вывод кода

Как видите, содержимое статьи успешно выводится, и все работает по плану.

В итоге файл /pages/article/[id].js должен выглядеть так:

import { url } from "../../config/next.config";
import ReactMarkdown from "react-markdown";

export default function Article({ article }) {
  return (
    <div>
      <h1>{article.Title}</h1>
      <ReactMarkdown>{article.Body}</ReactMarkdown>
    </div>
  );
}

export const getStaticProps = async (context) => {
  const data = await fetch(`${url}/articles/${context.params.id}`);
  const article = await data.json();

  return {
    props: { article },
    revalidate: 1,
  };
};
export async function getStaticPaths() {
  // Вызываем внешнюю конечную точку API для получения постов
  const res = await fetch(`${url}/articles`);
  const articles = await res.json();

  // Получаем на основе постов пути, которые нужно предварительно отрисовать
  const paths = articles.map((item) => ({
    params: { id: item.id.toString() },
  }));

  // В процессе сборки мы будем предварительно отрисовывать только эти пути
  // { fallback: false } means other routes should 404.
  return { paths, fallback: false };
}

Следующим этапом мы проработаем отображение страницы About Me.

Страница About Me

Этот процесс весьма прост. Создайте в каталоге /pages файл about.js.

В этом файле пропишите:

import { url } from "../config/next.config";
export default function About({ data }) {
  console.log(data);

  return (
    <div>
      <h1>About the author:</h1>
      {data.Body}
    </div>
  );
}

export const getStaticProps = async () => {
  const res = await fetch(`${url}/about-me`);
  const data = await res.json();

  return {
    props: { data },
    revalidate: 1,
  };
};
  • Строка 1: импортируем переменную url, указывающую на расположение экземпляра Strapi.
  • Строка 2: создаем функциональный компонент React. При этом также вносим пропс data.
  • Строка 8: отображаем поле Body объекта data.
  • Строки 13–21: используем статическую генерацию для получения соответствующих данных. Позже экспортируем эти данные в качестве пропсов.

Теперь перейдите к localhost:3000/about. Ожидаемый вывод:

Вывод кода

Все отлично работает!

Вот репозиторий этого проекта на GitHub.

Заключение

С появлением CMS, подобных Strapi, жизнь блогеров существенно упростилась. Главным образом благодаря тому, что они избавляют автора от необходимости копаться в коде для внесения в посты простых исправлений. Кроме того, CMS совместно с прекрасными фронтенд-инструментами вроде Next.js оказываются идеальным способом организовать блог в короткий промежуток времени.

Благодарю вас за внимание!

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Hussain Arif: Build Your Personal Blog With Next.js and Strapi

Предыдущая статьяКак стать разработчиком Go: в 6 шагах от карьеры
Следующая статья7 полезных операций в Pandas при работе с DataFrame