Разработка быстрых и современных сайтов на базе Next.js, с использованием GraphQL & WordPress

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

Введение

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

WordPress - популярная headless CMS, применяемая для различных проектов - от простых блогов до сложных приложений.

У нас был доступ к админке живого сайта на WordPress, шило в коде и непреодолимое желание поэксперементировать с Next.js.

Решение основано на статье (и шаблоне) Vercel Using Headless WordPress with Next.js and Vercel.

Получилось достаточно быстрое приложение с примерно небольшими трудозатратами.

TL DR

  • подключаем GraphQL в WordPress проекте

  • собираем фронтенд на базе Next.js

  • выполняем деплой на Vercel

  • наслаждаемся пре-деплоем в GitHub PRs

  • демо https://wpnext-example.vercel.app/

  • код https://github.com/evg-zlg/wpnext

MVP подход

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

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

  • дизайн блога взяли у kod.ru (блог про Телеграм, ТОН и проекты Павла Дурова)

  • дизайн ленда взяли у ton.org (блокчейн с теми же корнями)

  • контент взяли у wpcraft.ru - потому что был доступ в админку

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

Бэкенд: WordPress + GraphQL

WordPress, возможно, самый простой способ сборки бэкенда, но в нашем случае мы взяли готовый работающий сайт, в котором уже был какой-то контент. И сразу перешли к настройке GraphQL.

Настройка GraphQL на базе WordPress

Процесс настройки займёт условных 5 минут:

  • зайти в админку, в раздел плагинов

  • найти плагин WPGraphQL - установили

  • указать GraphQL Endpoint, остальные настройки - по умолчанию

Фронтенд: Next.js + TypeScript

Создаём проект

Официальной документацией для Next.js приложения рекомендуется использовать create-next-app.

В наших примерах используем TypeScript и папку pages. Аналогично будет работать и с эксперементальной директорией app в т.ч. на чистом JavaScript.

npx create-next-app@latest --typescript

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

Переходим в папку проекта (если ещё не) и в терминале выполняем npm run dev. Если нигде не промазали, должен запуститься сервер на порту 3000 (по умолчанию):

ready - started server on 0.0.0.0:3000, url: <http://localhost:3000>
event - compiled client and server successfully in 469 ms (170 modules)

Переходим по указанному адресу, убеждаемся что всё работает.

Теперь у нас есть простое Next.js приложение.

Формируем GraphQL запрос

Плагин WPGraphQL в CMS WordPress предоставляет IDE для формирования и тестирования запросов.

Открываем wp-admin и находим GraphQL.

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

Например, мы хотим получить 5 последних добавленных постов категории “development”:

  • нажимаем Query Composer, находим в дереве posts, раскрываем

  • ставим галочку first и указываем значение 5

  • раскрываем where, выбираем categoryName и указываем “development”

  • далее раскрываем nodes и выбираем нужные поля: slug, title, excerpt, date

  • URL картинки лежит чуть глубже - featureImage/node/sourceUrl

Далее запускаем выполнение запроса кнопкой Execute Query (или Ctrl+Enter) и в правой секции видим результат запроса.

 Query Composer в админке WordPress
Query Composer в админке WordPress

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

Если не указать параметр first, то WPGraphQL вернёт 10 постов.

Максимальное возвращаемое количество постов - 100, даже если указать first: 1000.

А если нужно получить больше 100 постов? Автор плагина WPGraphQL говорит, что большие запросы могут привести к проблемам с производительностью клиента и сервера и предлагает использовать пагинацию.

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

Теперь мы знаем структуру возвращаемых данных и можем описать интерфейсы:

  • IPostPreview без контента для списка постов

  • IPost с контентом для страницы поста

// types.ts

export interface IPostPreview {
  slug: string;
  title: string;
  excertp: string;
  featuredImage: {
    node: {
      sourceUrl: string;
    }
  }
  date: string;
}

export interface IPost extends IPostPreview {
  content: string;
}

Получаем данные из CMS WordPress

Всё готово для получения данных на стороне клиента.

Добавим функцию getPosts, использующую метод fetch:

// wp-api.ts 

export async function getPosts() {

  // определяем Content-Type для JSON
  const headers = { 'Content-Type': 'application/json' };

  // формируем GraphQL запрос
  const query = `
    query FavoriteBlogs { 
      posts {
        nodes {
          slug
          title
          excerpt
          date
          featuredImage {
            node {
              sourceUrl
            }
          }
        }
      }
    }  
  `;

  // Первым аргументом метода fetch указываем GraphQL ендпоинт,
  // который мы определили в настройках CMS.
  // Второй аргумент - объект запроса.
  const res = await fetch('<https://wpcraft.ru/graphql>', {
    headers,
    method: 'POST',
    body: JSON.stringify({
      query,
    }),
  });

  // получаем JSON из объекта Promise<Response>
  const json = await res.json();

  // возвращаем посты
  return json.data?.posts.nodes;
}

Заголовки и обработка ответа будут нужны во всех запросах, поэтому имеет смысл вынести этот код в функцию-обёртку fetchData, которая будет принимать текст запроса и возвращать данные:

// wp-api.ts 

async function fetchData(query: string) {
  const headers = { 'Content-Type': 'application/json' };

  const res = await fetch('&lt;https://wpcraft.ru/graphql&gt;', {
    headers,
    method: 'POST',
    body: JSON.stringify({
      query,
    }),
  });
  const json = await res.json();
  
  return json.data;
}

export async function getPosts() {
  const data = await fetchData(`
    query getPosts{
      posts {
        nodes {
          slug
          title
          excerpt
          date
          featuredImage {
            node {
              sourceUrl
            }
          }
        }
      }
    } 	
  `);
  return data.posts.nodes as IPostPreview[];
}

И т.к. мы не указали параметр first, WPGraphQL вернёт нам 10 постов.

Структура и маршрутизация

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

При клике на пост должна открываться страница с контентом поста.

Каждая страница с постом будет иметь свой уникальный URL, который будет формироваться динамически используя slug.

Сейчас в директории pages у нас есть файл index.tsx - это главная страница, которая открывается по адресу http://localhost:3000/. Роут - /.

В директорию pages добавляем файл [slug].tsx - тут мы будем отрисовывать каждый отдельный пост. Роут - /some-meaningful-post-title

Если на главной странице мы хотим разместить, допустим, лендинг, а список постов отображать на другом роуте, скажем, через префикс /blog, мы можем создать внутри pages директорию blog, в неё добавить index.tsx для списока постов (роут - /blog) и [slug].tsx для каждого поста (роут - /blog/uniq-post-slug).

Вот тут подробней про роутинг в Next.js.

Варианты генерации страниц в Next.js

Next.js поддерживает разные способы генерации страниц, рассмотрим SSR и SSG.

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

// [slug].tsx

import styles from './slug.module.scss';
  1. SSR (генерация на стороне сервера)

    Страница со списком постов

    Получаем список постов с помощью getServerSideProps, передаём через пропсы в компонент страницы Home:

    // pages/index.ts
    
    // опишем явно какие пропсы ожидаем в Home
    interface IHomeProps {
      posts: IPostPreview[];
    }
    
    export default function Home({ posts }: IHomeProps) {
    
      return (
        <main>
          {posts.map((post) => (
            // используем Link из 'next/link'
            <Link key={post.slug} href={/${post.slug}}>
              {post.title}
            </Link>
          ))}
        </main>
      );
    }
    
    // тип GetServerSideProps экспортируем из 'next'
    export const getServerSideProps: GetServerSideProps = async () => {
      // тип IPostPreview[] переменной posts можно не указывать,
      // т.к. мы явно указали в getPost какого типа данные мы возвращаем
      const posts: IPostPreview[] = await getPosts();
    
      return {
        props: {
          posts,
        },
      };
    }
    

    Теперь на страницу выводится кликабельный список заголовков постов, клик на пост открывает страницу [slug].tsx, URL меняется на http://localhost:3000/[slug].

    Страница контента поста

    Добавим функцию getPostBySlug, с её помощью мы будем получать пост с контентом для отображения на странице поста.

    // wp-api.ts
    
    export async function getPostBySlug(slug: string) {
      const data = await fetchData(`
        query getPostBySlug {
          post(id: &quot;${slug}&quot;, idType: SLUG) {
            title
            content
            excerpt
            slug
            featuredImage {
              node {
                sourceUrl
              }
            }
            date
          }   
      `});
      return data.post as IPost;
    }

    Теперь всё готово для получения поста и генерации страницы:

    // [slug].ts
    
    // опишем явно какие пропсы ожидаем в Post
    interface IPostProps {
      post: IPost;
    }
    
    export default function Post({ post }: IPostProps) {
    
      return (
        <>
          {post && (
            <article>
              <h1>{post.title}</h1>
              <div dangerouslySetInnerHTML={{__html: post.content}} />
            </article>
          )}
        </>
      );
    }
    
    export const getServerSideProps: GetServerSideProps = async (context) => {
      // params может быть undefined, slug может быть string | string[] | undefined
      // поэтому укажем явно какой тип мы передаём в slug
      // это немного "костыль", но пока так
      const slug = context.params?.slug as string;
    
      // тип для переменной post не указываем, т.к. в getPostBySlug указали 
      // какого типа данные возвращаем
      const post = await getPostBySlug(slug);
    
      return {
        props: {
          post
        }
      }
    }

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

    Стилизуем контент:

    // slug.module.scss
    
    .content {
      p,
      ul,
      ol,
      blockquote {
        margin: 1.5rem 0;
      }
      a {
        cursor: pointer;
        text-decoration: underline;
      }
      ul,
      ol {
        padding: 0 0 0 1rem;
      }
      ul {
        list-style-type: disc;
      }
      ol {
        list-style-type: decimal;
      }
      pre {
        white-space: pre;
        overflow-x: auto;
        padding: 1rem;
        font-size: 1.25rem;
        line-height: 1.25;
        border: 1px solid rgb(156 163 175);
        background-color: rgb(243 244 246);
      }
      code {
        font-size: 0.875rem;
        line-height: 1.25rem;
      }
      figcaption {
        text-align: center;
        font-size: 0.875rem;
        line-height: 1.25rem;
      }
    ...
    // [slug].tsx
    
    ...
    <div
      className={styles.content}
      dangerouslySetInnerHTML={{__html: post.content}}
    />
    ...

    Мы пока оставляем за скобками вопрос о пробросе стилей сформированных в WordPress с помощью Gutenberg, т.к. автор ещё сам не разобрался как это сделать

Источник: https://habr.com/ru/articles/738074/


Интересные статьи

Интересные статьи

Сегодня пятница, а значит, можем подумать о чем-то, кроме работы. На Хабре многие увлекаются электроникой, создавая собственные DIY-проекты, от серьезных научных разработок до «я сделал это, потому ...
Компания Megawin Technology Co., Ltd. была основана в Тайване в 1999 году.С 2004 г. было запущено массовое производство 8-битных микроконтроллеров (МК).На российском рынке компания известна прежде все...
Весной этого года корпорация IBM заявила о разработке процессора по 2-нм техпроцессу. Причем это были не просто слова, компания продемонстрировала тестовые образцы чипа. Правда, анонс подвергли крит...
Перевод статьи из блога Кена Ширрифа В лунных миссиях «Аполлон» ракетой Сатурн-5 управлял передовой бортовой компьютер, разработанный в IBM. Система собиралась из гибридных модулей, похожих на...
Вчера, полностью серьезно и без каких-либо шуток-прибауток, компания Cloudflare анонсировала свой новый продукт — VPN-сервис на базе DNS-приложения 1.1.1.1 для мобильных устройств с использование...