JAMstack для начинающих

Опубликовано 30 июля 2022 в 11:02 (Обновлено 13 ноября 2023 в 01:55)

Время чтения: 27 мин

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

JAMstack
JAMstack

Введение

JAMstack - это архитектура и философия программного обеспечения, которая придерживается следующих компонентов:

  • Javascript,
  • API,
  • Markup.

Если это звучит знакомо, то потому, что это так! Например, у вас React приложение, которое вы компилируете с Webpack и, в конечном счете, обслуживаете из S3? Да, это приложение JAMstack.

Простой HTML файл, который не имеет JavaScript и буквально не делает ничего динамического? Да, это также приложение JAMstack.

Если вы больше ориентируетесь на облачные технологии (например, AWS, GCP, Azure), вы, возможно, склонны думать о бессерверности и JAMstack как об одном и том же. Конечно, у них есть общие черты в философии управления ресурсами, например, в хостинге сайта на S3. Но приложение JAMstack не всегда будет бессерверным.

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

Сочетание этих философий может иметь большое значение, но их не следует путать и представлять как одно и то же.

Составляющие JAMstack

Вернемся к стеку JAM: он обычно состоит из трех компонентов: Javascript, API и Markup. Его история связана с превращением термина «статический сайт» во что-то более значимое (и востребованное на рынке).

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

Хотя нет какого-либо определенного набора инструментов, которые вам нужно использовать, или каких-либо инструментов, кроме простого HTML, есть отличные примеры того, что может составлять каждую часть стека. Давайте немного углубимся в каждый компонент.

Javascript

Компонент, который, вероятно, проделал большую часть работы по популяризации JAMstack, - это Javascript. Наш любимый язык браузера позволяет нам предоставлять все динамические и интерактивные элементы, которых у нас могло бы не быть, если бы мы обслуживали простой HTML без него.

Именно здесь вы часто будете видеть, как в игру вступают UI-фреймворки, такие как React, Vue, и новички, такие как Svelte.

Пример "Простой компонент" с сайта reactjs.org
Пример "Простой компонент" с сайта reactjs.org

Они делают создание приложений более простым и организованным, предоставляя API-интерфейсы компонентов и инструменты, которые компилируются в простой HTML-файл (или их набор).

Эти HTML-файлы включают в себя группу ресурсов, таких как изображения, CSS и фактический JS, которые в конечном итоге передаются браузеру через ваш любимый CDN (сеть доставки контента).

API

Использование сильных сторон API - это основа того, как сделать приложение JAMstack динамичным. Будь то аутентификация или поиск, ваше приложение будет использовать Javascript для отправки HTTP-запроса другому провайдеру, что в конечном итоге улучшит работу в той или иной форме.

В Gatsby придумали фразу «контентная сетка», которая довольно хорошо описывает возможности.

Вам не обязательно обращаться только к одному хосту для получения API, но вы можете обратиться ко многим, сколько вам нужно (но постарайтесь не переусердствовать).

Например, если у вас есть безголовый (headless) WordPress API, в котором вы размещаете свои записи блога, учетную запись Cloudinary, в которой вы храните свои специализированные мультимедийные файлы, и Elasticsearch, который предоставляет функции поиска, все они работают вместе, чтобы предоставить качественный и единый опыт для людей, использующих ваш сайт.

Markup

Markup - разметка. Это критическая часть. Будь то написанный от руки HTML или код, который компилируется в HTML, это первая часть, которую вы предоставляете клиенту. Это своего рода де-факто часть любого сайта, но то, как вы его обслуживаете, является самой важной частью.

Чтобы считаться приложением JAMstack, HTML необходимо обслуживать статически, что в основном означает, что он не будет динамически отрисовываться с сервера.

Если вы собираете страницу вместе и обслуживаете ее с помощью PHP, то, скорее всего, это не приложение JAMstack. Если вы загружаете и обслуживаете один HTML-файл из хранилища, в котором создается приложение с помощью Javascript, это звучит как приложение JAMstack.

Статический вывод Gatsby на AWS S3
Статический вывод Gatsby на AWS S3

Но это не значит, что мы всегда должны собирать 100% приложения внутри браузера. Такие инструменты, как Gatsby и другие статические генераторы сайтов, позволяют нам извлекать некоторые или все наши источники API во время сборки и выводить страницы в виде HTML-файлов.

Подумайте, если у вас есть блог WordPress, мы можем извлечь все записи и, в конечном счете, создать новый HTML файл для каждой записи. Это означает, что мы сможем обслуживать прекомпилированную версию страницы непосредственно в браузере, что обычно приравнивается к более быстрой первой отрисовке и более быстрому опыту для вашего посетителя.

Мы говорили выше о 3-х компонентах (Javascript, API, Markup), но о чем мы не говорили, так это о том, что вам не обязательно использовать все 3 из них, чтобы считать ваш сайт достойным названия JAM.

На самом деле все сводится к разметке и тому, как вы ее обслуживаете. Вместо того, чтобы ваше приложение Rails отображало ваш HTML-код для вас, вы можете разместить на S3 предварительно скомпилированное приложение React, которое обращается к Rails через набор API.

Но вам даже не нужны API. Вам даже не нужен Javascript! Пока вы обслуживаете HTML-файл без необходимости его компилирования на сервере во время запроса (то есть предварительного рендеринга), у вас есть сайт JAMstack.

Хостинг

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

Вместо этого, независимо от того, делаете ли вы это самостоятельно с помощью S3 или подключаете его к Netlify (который на самом деле является мульти-облачным), ваши HTML и статические ресурсы обслуживаются из объектного хранилища. В конечном счете, у вас обычно есть CDN, такой как Cloudflare, который кэширует эти файлы в местах по всему миру, ускоряя загрузку страниц для людей, посещающих ваш сайт.

Карта распределения CDN (пример)
Карта распределения CDN (пример)

Почему JAMstack

Приложения JAMstack по своей сути удовлетворяют большинству, если не всем, из пяти основных принципов AWS Well-Architected Framework (хорошо структурированный фреймворк). Это основные концепции, которые AWS рассматривает для создания быстрой, безопасной, высокопроизводительной, отказоустойчивой и эффективной инфраструктуры.

AWS с хорошей архитектурой
AWS с хорошей архитектурой

Скорость

Тот факт, что вы обслуживаете приложения JAMstack как статические файлы непосредственно из CDN (обычно), делает вероятным то, что ваше приложение будет загружаться очень быстро. Прошли те времена, когда серверу приходилось тратить время на создание страницы, прежде чем отвечать; теперь вы обслуживаете страницу в виде простого HTML «как есть» или с некоторым типом гидратации на стороне клиента, как в случае с React.

Стоимость

Чаще всего сайты JAMstack будет запустить дешевле, чем их серверные аналоги. Размещение статических ресурсов стоит недорого, а страница обслуживается с той же скоростью, если не быстрее.

Масштабируемость

Поскольку вы обслуживаете свои файлы со статического хостинга, скорее всего, через CDN, это в значительной степени автоматически дает вам бесконечную масштабируемость. Большинство провайдеров с этим помогают, а это значит, что у вас не будет проблем с тем, чтобы любой поток людей, попавших на ваш сайт, смог с ним полноценно работать.

Обслуживание

Основа вашего статического сайта - не сервер, а это значит, что вам не нужно его обслуживать. Будь то Netlify, S3 или любой другой провайдер, ваш статические HTML, CSS и JS поддерживаются без головной боли.

Безопасность

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

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

Зависимость от API

Несмотря на то, что эти положения выше справедливы для статических аспектов вашего сайта, имейте в виду, что вы все равно можете зависеть от какого-либо типа API для работы на стороне клиента.

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

Примеры JAMstack

freecodecamp.org

Да! freecodecamp.org и его обучающий портал - это сайт JAMstack, построенный на Gatsby. Даже несмотря на сложности предоставления приложения для прохождения курсов по программированию, freeCodeCamp может объединить мощь генератора статических сайтов и мощных API-интерфейсов, чтобы дать людям во всем мире возможность изучать код.

Вы можете увидеть, как Куинси из freeCodeCamp рассказывает об этом больше на JAMstack_conf 2018:

Примечание: порталы новостей и форумов в настоящее время не являются сайтами JAMstack.

impossiblefoods.com

Главный сайт Impossible Foods - это не что иное, как сайт Gatsby! Все, от их рецептов до средства поиска местоположения, скомпилировано с помощью нашего любимого «молниеносного» генератора статических сайтов.

web.dev

Ресурсный центр Google web.dev создан с использованием растущего по числу пользователей 11ty. Вы даже можете найти код с открытым исходным кодом по адресу:

https://github.com/GoogleChrome/web.dev

Инструменты

Хорошая новость заключается в том, что в настоящее время доступно множество инструментов, и еще много на подходе. Они могут все еще быть немного "грубыми", но это потому, что это новый дивный мир инструментов, и его нужно немного сгладить, чтобы добиться нужного результата.

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

Это самое интересное. Как вы собираетесь создавать свое приложение? Со Scully вы можете выбрать свой любимый вариант UI-фреймворка и сразу приступить к работе. Вот несколько популярных, но далеко не исчерпывающих вариантов:

Обслуживание вашего приложения

Мне нравится думать об этом как о простой части, зависящей от ваших настроек. Такие инструменты, как Netlify и Zeit, упрощают настройку, подключаясь к вашему репозиторию Github и создавая каждый раз, когда появляется новый коммит, но, конечно, у вас есть такие опции, как AWS, если вы хотите большего контроля.

Делаем приложение динамичным

На самом деле это может быть что угодно, что можно использовать в качестве API для запросов из браузера. Я не собираюсь приводить кучу примеров для каждого типа, но вот несколько инструментов и мест, где вы можете найти нужные ресурсы.

Далее мы более детально разберем варианты решений на Jamstack и создадим полезное приложение.

Создание приложения

В этом блоке нашего руководства мы рассмотрим как использовать фреймворк React Next.js со Strapi для создания веб-сайта портфолио.

Next.js - это легкий фреймворк для статических и серверно-рендерных React-приложений, также созданный на React.

Особенностью Strapi является то, что это фреймворк с открытым исходным кодом, основан на NodeJS и создан разработчиками для использования фронтенд-разработчиками.

Что будем делать

Не имеет значения, является ли создаваемый вами сайт личным, сайтом-портфолио или сайтом компании. Применяются те же инструменты.

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

Strapi CMS - это так называемая автономная CMS или безголовая система управления, т.е. headless. По сути, это означает, что это серверная служба вашего сайта.

Другие CMS, такие как WordPress, включают общедоступную часть сайта в то же приложение, что и серверную часть. Это часто приводит к тому, что веб-сайт становится медленным и сложным в управлении. Но как разработчик JS, который недолюбливает PHP, я могу быть здесь немного предвзятым.

WordPress теперь поддерживает использование себя в качестве автономной безголовой CMS. Однако я не буду пробовать это из-за… ну, знаете… PHP.

В любом случае, давайте начнем создание сайта с Next.js. Репозиторий проекта:

https://github.com/Devalo/create-portfolio-with-next-strapi

Планирование

Так что именно мы будем создавать? Как упоминалось ранее, простое онлайн-портфолио. Мы должны иметь возможность входить в систему и записывать, редактировать и удалять записи. Посетители будут просматривать все записи и нажимать на конкретную запись, чтобы просмотреть всю историю.

Фронтенд будет написан в Next.js версии 10. Он будет запрашивать данные у Strapi API. Мы будем использовать Bootstrap 5 для нашего макета. Bootstrap 5 только что вышел из бета-версии, так что мы должны быть готовы к работе с ним!

После некоторых зарисовок на бумаге, макет будет выглядеть примерно так:

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

Создаем новый проект Next.js

Для создания нового проекта Next.js необходимо сначала установить Node.js. Если он еще не установлен, посетите сайт https://nodejs.org/ и установите Node.js перед тем, как продолжить.

Next.js поставляется с очень хорошим генератором проекта. Мы создадим наш новый проект, набрав:

$ npx create-next-app my-portfolio

Это создаст для нас новый проект. Мы переходим внутрь него (cd) и запускаем:

$ cd my-portfolio
my-portfolio $ npm run dev

Если вы посетите http://localhost:3000/, вы должны увидеть стартовую страницу Next.js:

Установка Bootstrap 5

Как упоминалось ранее, мы собираемся использовать Bootstrap 5 для наших проектов. Мы можем начать с установки пакета Bootstrap 5 NPM:

$ npm install bootstrap@next

Внутри нашего файла /pages/_app.js мы импортируем только что установленную библиотеку:

// /pages/_app.js
import '../styles/globals.css'
import 'bootstrap/dist/css/bootstrap.min.css'; // Added
function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}
export default MyApp

Чтобы убедиться, что это работает, мы можем заменить все внутри файла index.js простым контейнером bootstrap и текстом:

// /pages/index.js
export default function Home() {
  return (
    <div className="container">
      Is this working?
    </div>
  );
};

Если вы сейчас посмотрите на http://localhost: 3000/, то вы увидите, что наша строка текста смещена к середине. Если мы удалим контейнер из нашего класса div, текст будет прижат слева нашей страницы. Это означает, что наш Bootstrap запущен и работает.

Axios

Мы будем общаться со Strapi, отправляя и получая JSON. Мы будем использовать "Axios" для выполнения тяжелой работы. Давайте установим его:

$ npm install axios

Вот и все, что нужно для создания нашего фронтэнда. Давайте начнем создавать интерфейс фронтенда.

Создание визуала

Мы начнем с создания статической верхней панели навигации. Мы не собираемся делать ничего особенного, а воспользуемся встроенными в Bootstrap компонентами. Мы создадим отдельный файл макета, который будет содержать наши основные элементы макета.

Мы создадим новую папку в корневом каталоге под названием components. В каталоге компонентов мы создадим новый файл, который назовем Layout.jsx:

// /components/Layout.jsx

const Layout = ({ children }) => (
  <div>
    {children}
  </div>
)
export default Layout;

Все содержимое нашей страницы будет передано в этот компонент в качестве аргумента. Другими словами, мы обернем содержимое нашей страницы внутри этого компонента Layout. Давайте импортируем этот компонент в наш индексный файл и поместим в него наш контент:

// /pages/index.js

import Layout from '../components/Layout';

export default function Home() {
  return (
    <Layout>
      <div className="container">
        Is this working?
      </div>
    </Layout>
  );
};

Если вы перейдете на http://localhost:3000/, все должно выглядеть одинаково.

Создаем панель навигации

Внутри нашего каталога компонентов мы создадим новый. Мы назовем его shared. Внутри этого каталога мы создадим новый файл, который мы назовем Navbar.jsx. Мы создадим нашу навигацию (Navbar) здесь.

// components/shared/Navbar.jsx
const Navbar = () => (
  <nav className="navbar navbar-expand-lg navbar-dark bg-primary fixed-top">
    <div className="container">
      <a className="navbar-brand" href="#">My Portfolio</a>
      <button
        className="navbar-toggler"
        type="button"
        data-bs-toggle="collapse"
        data-bs-target="#navbarText"
        aria-controls="navbarText"
        aria-expanded="false"
        aria-label="Toggle navigation"
      >
        <span className="navbar-toggler-icon" />
      </button>
      <div className="collapse navbar-collapse" id="navbarText">
        <ul className="navbar-nav me-auto mb-2 mb-lg-0">
          <li className="nav-item">
            <a className="nav-link active" aria-current="page" href="#">Portfolio</a>
          </li>
          <li className="nav-item">
            <a className="nav-link" href="#">About me</a>
          </li>
        </ul>
      </div>
    </div>
  </nav>
);
export default Navbar;

И мы импортируем и добавляем его в наш файл макета (Layout):

// components/Layout.jsx
import Navbar from './shared/Navbar';
const Layout = ({ children }) => (
  <div>
    <Navbar />
    <div className="main-container container">
      {children}
    </div>
  </div>
)
export default Layout;

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

Создание интерфейса (UI)

Давайте теперь обсудим, как мы собираемся разместить все элементы портфолио. Я подумываю разделить все на строки и столбцы, где в каждой строке по 2 элемента. Мы можем изменить наш index.js на:

// pages/index.js
import Layout from '../components/Layout';
export default function Home() {
  const entries = ['Project 1', 'Project 2', 'Project 3', 'Project 4'];
  return (
    <Layout>
      <div className="entries">
        <div className="row justify-content-start  ">
          {entries.map((entry) => (
            <div className="col-md-6">
              <div className="entry mb-3">
                <div className="main-image">
                  <img src="https://via.placeholder.com/600x400" />
                  <h1>{entry}</h1>
                  <p>
                    About
                    {' '}
                    {entry}
                  </p>
                </div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </Layout>
  );
}

Давай посмотрим что здесь происходит. Мы смоделируем 4 записи портфолио с помощью массива. Чуть позже мы заменим массив фактическим содержимым.

Мы добавим в это немного CSS. В папке public/ или в папке styles/ вы найдете файл css с именем globals.css, содержимое которого мы удалим и заменим на:

// publi/globals.css
.main-container {
  padding-top: 70px;
}
.entries .main-image img {
  width: 100%;
  display: block;
}

У нас должно получиться что-то вроде этого:

Сейчас мы просто используем изображения-заполнители. Позже мы заменим их подходящими изображениями, а также воспользуемся компонентом Image в Next.js.

Интерфейс (UI) для одного элемента портфолио

Теперь, когда у нас есть главная страница, на которой мы можем отображать все элементы нашего портфолио, нам следует подумать о создании страницы, на которой будет отображаться один элемент портфолио.

Здесь вступает в игру система маршрутизации Next.js. Видите ли, каждый файл в каталоге pages - это маршрут. Поэтому, если вы создадите файл и компонент testpage.jsx и перейдете по адресу http://localhost:3000/testfile, вы должны увидеть содержимое компонента.

Это безумно круто, и это одна из вещей, которые делают Next.js таким хорошим. Это также работает с динамическим содержимым. Если вы заключите имя файла в квадратные скобки, NextJS будет рассматривать его как динамический файл.

Мы создадим новую папку в папке pages, которую мы называем portfolio. Внутри этой папки мы создадим файл, который будем называть [slug].jsx . Этот файл будет содержать отдельные элементы нашего портфолио. Чтобы проверить его динамические возможности, мы можем написать компонент для быстрого тестирования:

// pages/portfolio/[slug].jsx

const PortfolioItem = () => {
  return (
    <div>
      This is a test component
    </div>
  );
};
export default PortfolioItem;

Независимо от того, какой URL вы используете, Next.js будет отображать один и тот же компонент.

http://localhost:3000/portfolio/(45231
http://localhost:3000/portfolio/test
http://localhost:3000/portfolio/fsdfsdfsdfs

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

// pages/portfolio/[slug].jsx

import Layout from '../../components/Layout';
const PortfolioItem = () => {

  return (
    <Layout>
      <div className="row">
        <div className="portfolio-image text-center mb-4">
          <div className="col-md-12">
            <img src="https://via.placeholder.com/1000x500" />
          </div>
        </div>
      </div>
      <div className="row">
        <div className="portfolio-content">
          <div className="col-md-12">
            <div className="portfolio-headline text-center m-2">
              <h1>Project</h1>
            </div>
            <p>
              Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium
              doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis
              et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem
              quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores
              eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem i
              psum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius
              modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut
              enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit
              laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure
              reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur,
              vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
            </p>
          </div>
        </div>
      </div>
    </Layout>
  );
};
export default PortfolioItem;

По большей части это заполнитель для наших реальных данных. Отдельная страница портфолио должна теперь выглядеть примерно так:

Никогда не говорил, что это будет выглядеть красиво.

На нашей главной странице отображаются все объекты портфолио. У нас также есть страница с одним элементом портфолио. Мы должны развернуть и запустить наш бэкэнд Strapi прежде чем мы вернемся к нашему фронтэнду приложения. Как только мы запустим бэкэнд Strapi, мы сможем наполнить его контентом, который будет использован во фронтенде.

Создание движка

Установка

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

Strapi имеет собственный генератор проектов. Мы выйдем (cd) из нашего внешнего приложения и создадим серверную часть бок о бок:

my-portfolio $ cd ..
$ yarn create strapi-app portfolio-backend --quickstart

После установки сервер запустится автоматически. Вы попадете прямо на новую вкладку в вашем браузере:

Это будет вашей главной информацией для входа в систему. Заполните ее должным образом, чтобы не столкнуться с какими-либо проблемами во время разработки.

Потрясающе! После того, как вы создадите свой аккаунт, вы должны увидеть главную панель инструментов Strapi:

По сути, это графический интерфейс для вашей базы данных. Это означает, что мы можем создавать таблицы с содержимым, как в обычной базе данных.

Коллекции

В левом меню, под Plugins, вы увидите Content-Type Builder. Здесь вы сможете создавать различные типы данных.

Тип Single - это как раз то, на что он похож. Это единый тип данных. Это может быть фрагмент статического текста или что-то подобное.

Тип Коллекция (Collection) - это тип, у которого много элементов. В нашем случае тип Коллекция будет содержать много элементов портфолио. Это также было бы правильным выбором, если бы вы создали блог, газету или что-нибудь еще с несколькими записями, которые вы бы повторяли во фронтенде.

Давайте настроим тип коллекции - Портфолио.

Нажмите кнопку "Создать новый тип коллекции" (Create a new collection type) и введите имя коллекции.

Когда закончите, нажмите «Продолжить» (Continue). Затем нам нужно выяснить, какие поля данных мы хотим использовать. Я думаю об этих вещах:

  • Headline
  • Content
  • Image
  • slug id

Для заголовка мы создадим новое текстовое поле Text field и назовем его "Headline" (заголовок).

Для нашего контента мы создадим текстовое поле Rich text:

Для нашего основного изображения мы создадим поле Media:

Для нашего слага (url) мы создадим поле id и установим для него Headline (Заголовок):

Это должно дать нам 4 поля, и мы можем создать их в итоге нажав «Сохранить» (Save):

Сервер перезапустится и внесет все наши изменения. После этого вы заметите, что наш тип портфолио указан в списке типов коллекций в левом меню:

Вы видели, что оно было написано во множественном числе автоматически? Это потому, что оно может содержать несколько портфолио. Этого бы не случилось с типом Single.

Разрешения

Разрешения или Permissions. Следующим шагом будет добавление разрешений. По умолчанию Strapi полностью закрыт, поэтому вам нужно вручную выбрать, какие типы запросов должны быть общедоступными. В левом меню нажмите «Настройки» (Settings), затем нажмите «Роли» Roles в разделе «Плагин пользователей и разрешений» (Users & Permissions Plugin).

Здесь мы можем установить разрешения для аутентифицированных и общедоступных API. Поскольку мы собираемся создавать элементы нашего портфолио непосредственно внутри нашей CMS, нам не нужно беспокоиться об аутентификации. Выберите здесь Public:

Мы хотим, чтобы общедоступный API был открыт только для просмотра всех или отдельных элементов портфолио:

Добавляя find и findone мы делаем роуты /portfolios/:id и /portfolios доступными. Нажмите save и переходите к нашему новому типу коллекции Portfolios (Портфолио).

Добавление элемента портфолио

У нас еще нет элементов портфолио. Давайте создадим один! Кликните на Add New Portfolio в правом верхнем углу. Нам также понадобится изображение. Я взял случайное фото, которое я буду использовать:

https://unsplash.com/photos/tZc3vjPCk-Q

Мы можем добавить новое изображение, щелкнув в правом верхнем углу поля изображения:

Загрузить ресурсы:

Мы можем выбирать между загрузкой с локального компьютера или по URL-адресу. Я добавляю URL изображения:

И выберите загрузить и завершить (upload):

Это должно добавить наше изображение прямо в наше портфолио. Заполним и остальные поля.

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

Щелкните "Сохранить" (Save). Элемент портфолио создан. Нам также нужно нажать «Опубликовать» (Publish), чтобы он стал доступен через API.

Если вы перейдете по адресу http://localhost:1337/portfolios, вы увидите нашу недавно созданную запись портфолио в формате JSON.

В следующем разделе мы вернемся к нашему интерфейсу.

Не забывайте, что ваш бэкэнд должен работать, пока вы работаете с фронтендом.

Фронтенд: чтение и отображение данных

В этой части статьи вы увидите, как великолепно работают вместе Strapi и NextJS. Вот где они демонстрируют свою силу!

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

Сначала мы напишем функцию, которая будет получать данные из нашей CMS. В корневом каталоге создайте новую папку lib и в ней файл service.js. Этот файл будет отвечать за выборку данных из CMS. Я использую для этого axios. Если вам больше нравится библиотека fetch, продолжайте и используйте ее.

// lib/service.js
import axios from 'axios';
const fetchFromCMS = async (path) => {
  const url = `http://localhost:1337/${path}`;
  const res = await axios.get(url);
  return res.data;
};
export default fetchFromCMS;

Таким образом, мы в основном просто выполняем запросы GET к нашей CMS, извлекая элементы. Мы должны иметь возможность получать все из нашего файла index.js. Посмотрим, как это сделать.

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

Что делает NextJS таким хорошим, так это его способность отображать материалы на стороне сервера. Таким образом мы будем отображать элементы нашего портфолио.

getStaticProps() - предпочтительный способ получения статических данных из CMS. В нашем файле index.js мы импортируем нашу новую функцию выборки и реализуем getStaticProps():

// pages/index.js
// .....
import fetchFromCMS from '../lib/service';
export default function Home({ portfolioItems }) {
  console.log(portfolioItems);
  // ....
}
export async function getStaticProps() {
  const portfolioItems = await fetchFromCMS('portfolios');
  return {
    props: { portfolioItems },
    revalidate: 1,
  };
}

Мы получаем данные из нашей функции fetchFromCMS() и передаем их в качестве аргумента в главную функцию. Это происходит автоматически. Свойство revalidate в основном сообщает Next.js, как часто он должен регенерировать эту страницу и данные. Если вы установите его в 30, то он будет перестраиваться каждые 30 секунд. Это означает, что новые записи не будут видны до тех пор, пока приложение не перестроится. Это довольно круто. Это означает, что мы подаем клиенту чистый HTML, js и CSS. Без извлечения базы данных. Это сделает сайт быстрым.

Кроме того, функция getStaticProps() работает на стороне сервера, в то время как главная функция работает на стороне клиента. Симпатично.

При обновлении страницы содержимое нашей базы данных попадает в консоль браузера:

Безумно круто! Давайте визуализируем это в нашем компоненте React:

// pages/index.js
// ...
export default function Home({ portfolioItems }) {
  return (
    <Layout>
      <div className="entries">
        <div className="row justify-content-start ">
          {portfolioItems.map((portfolio) => (
            <div className="col-md-6">
              <div className="entry mb-3">
                <div className="main-image">
                  <Image
                    src={portfolio.image.name}
                    width={600}
                    height={400}
                    alt={portfolio.Headline}
                  />
                  <h1>{portfolio.Headline}</h1>
                </div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </Layout>
  );
}
// ....

Мы перебираем элементы портфолио, как мы делали это с элементами-заполнителями. Обратите внимание, что мы больше не используем тег <img />.

NextJS предлагает лучший вариант! Их недавно выпущенный компонент React сгенерирует файл в формате .webp из вашего изображения, что приведет к его более быстрой загрузке. Он также поставляется с включенной отложенной загрузкой, и будет адаптивным! Если я правильно понял, обязательные поля width и height принимают значения max-width и max-height.

Однако компонент изображения немного строгий. Вы не можете неявно добавлять изображения извне приложения без внесения URL-адреса в белый список. Итак, что нам нужно сделать дальше - это создать файл конфигурации в нашем корневом каталоге next.config.js и поместить в него следующее:

// config.next.js

module.exports = {
  images: {
    domains: ['images.unsplash.com'],
  },
};

Next.js будет жаловаться и даст вам точный URL, который вы должны ввести. В этом случае, я занесу в белый список URL Unsplash. Потрясающе. Вам нужно перезапустить приложение.

Мы получили все данные, и разместили все как надо. Вы удивлены? Я пошел дальше и создал еще несколько элементов портфолио:

Просмотр отдельных элементов портфолио

Готовы к еще одному волшебству? Мы должны сделать наши элементы портфолио кликабельными. Когда вы нажимаете на элемент, вы попадаете на страницу этого элемента. На нашей странице index.js мы используем встроенный в Next.js компонент Link. Он работает так же, как и react-router. Мы завернем наши элементы в компонент:

// pages/index.js
import Link from 'next/link';
// ....
export default function Home({ portfolioItems }) {
  return (
    <Layout>
      <div className="entries">
        <div className="row justify-content-start  ">
          {portfolioItems.map((portfolio) => (
            <div className="col-md-6">
              <div className="entry mb-3">
                <Link as={`/portfolio/${portfolio.slug}`} href="/portfolio/[id]"> // this gets added 
                  <div className="main-image">
                    <Image
                      src={portfolio.image.name}
                      width={600}
                      height={400}
                      alt={portfolio.Headline}
                    />
                    <h1>{portfolio.Headline}</h1>
                  </div>
                </Link> // this gets added
              </div>
            </div>
          ))}
        </div>
      </div>
    </Layout>
  );
}
// ....

Если вы попытаетесь нажать на какой-либо из элементов, вы попадете на страницу элемента, которую мы создали ранее. Давайте посмотрим, как мы можем заполнить её правильными данными.

В нашем [slug] .js мы собираемся использовать две специальные серверные функции. getStaticProps(), с которой мы уже знакомы, и getStaticPaths), которая будет строить динамические маршруты во время сборки.

// pages/portfolio/[slug].jsx
// ....
export async function getStaticProps({ params }) {
  const portfolio = await fetchFromCMS(`portfolios?slug=${params.slug}`);
return {
    props: { portfolio: portfolio[0] },
    revalidate: 1,
  };
}

getStaticProps() очень похожа на ту, которая находится в индексном файле, за исключением того, что мы выбираем один элемент по имени ярлыка портфолио, который мы создали в Strapi. Это массив, поэтому мы передадим данные с индексом 0 в клиентскую функцию.

// pages/portfolio/[slug].jsx
// ....
export async function getStaticPaths() {
  const portfolios = await fetchFromCMS('portfolios');
return {
    paths: portfolios.map((portfolio) => ({
      params: {
        slug: portfolio.slug,
      },
    })),
    fallback: false,
  };
}
// ....

Это будет использовано во время сборки. Она извлекает все портфолио и создает URL из имени slug. Мы сможем получать статические страницы вместо того, чтобы каждый раз запрашивать данные у базы данных. Обратите внимание, что мы использовали revalidate в функции getStaticProps(). Скорее всего, вы захотите установить его на что-то большее, чем 1 в производстве, в противном случае это вроде как сбивает всю цель.

Теперь мы можем использовать переданный аргумент в нашей клиентской функции. Весь [slug].jsx теперь выглядит так:

// pages/portfolio/[slug].js
import Image from 'next/image';
import Layout from '../../components/Layout';
import fetchFromCMS from '../../lib/service';
const PortfolioItem = ({ portfolio }) => {
  return (
    <Layout>
      <div className="row">
        <div className="portfolio-image text-center mb-4">
          <div className="col-md-12">
            <Image
              src={portfolio.image.name}
              width={1000}
              height={500}
            />
          </div>
        </div>
      </div>
      <div className="row">
        <div className="portfolio-content">
          <div className="col-md-12">
            <div className="portfolio-headline text-center m-2">
              <h1>{portfolio.Headline}</h1>
            </div>
            <div dangerouslySetInnerHTML={{ __html: portfolio.content }}/>
          </div>
        </div>
      </div>
    </Layout>
  );
};
export async function getStaticPaths() {
  const portfolios = await fetchFromCMS('portfolios');
return {
    paths: portfolios.map((portfolio) => ({
      params: {
        slug: portfolio.slug,
      },
    })),
    fallback: false,
  };
}
export async function getStaticProps({ params }) {
  const portfolio = await fetchFromCMS(`portfolios?slug=${params.slug}`);
return {
    props: { portfolio: portfolio[0] },
    revalidate: 1,
  };
}
export default PortfolioItem;

И если вы попробуете:

Данные отображаются правильно!

Данные представлены в формате Markdown. Чтобы их правильно отформатировать, вам нужно использовать какой-то парсер Markdown. Что вы можете сделать, так это добавить новый файл в вашу папку lib с функцией, которая обрабатывает текстовое содержимое, прежде чем вы передадите его клиентской функции. Например:

// lib/processMarkdown.js
import remark from 'remark';
import html from 'remark-html';

// process markdown to html and return
const processMarkdown = async (markdown) => {
  const process = await remark()
    .use(html, {sanitize: true})
    .process(markdown.content);

  return process.toString();
};

export default processMarkdown;

Это было весело. Я надеюсь, что это было так же весело, как и писать это руководство. Next.js и Strapi - потрясающая комбинация и моя технология для создания быстрых и надежных динамических веб-сайтов. А что лучше всего?

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

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

То же самое можно сказать и о Next.js. Это невероятно удобная штука для создания высокоэффективных веб-сайтов, которые выводят динамический контент (который ведет себя как статический). И его очень легко развернуть. Я рекомендую разместить ваше приложение NextJS на Vercel. Поддержка со стороны ребят из NextJS тоже отличная.

Мы использовали Bootstrap 5, но лишь поверхностно. Если хотите, я всегда могу написать об этом в блоге. Однако самое замечательное в Bootstrap 5 - это то, что он больше не поставляется с jQuery, что, на мой взгляд, является очень хорошим решением команды Bootstrap.

Репозиторий проекта:

https://github.com/Devalo/create-portfolio-with-next-strapi

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.