20 квітня Java Hiring Challenge у Дніпрі. Отримай Job Offer в 1 день

Детально про Gatsby.js — інструмент для створення складних веб-сайтів

Як відомо, на одних бойлерплейтах далеко не заїдеш, тому доводиться лізти вглиб будь-якої технології, щоб навчитися писати щось вартісне. У цій статті розглянемо деталі Gatsby.js, знання яких дозволить вам створювати і підтримувати складні веб-сайти і блоги.

Теми, розглянуті нижче:

Підготовка

Встановлення Gatsby на ПК
yarn global add gatsby-cli
Клонування мінімального проекту
npx gatsby new gatsby-tutorial https://github.com/gatsbyjs/gatsby-starter-hello-world
cd gatsby-tutorial
Ініціалізація репозиторію
git init
git add .
git commit -m "init commit"
Перевірка справності
yarn start

Якщо в консолі немає помилок, а в браузері на http://localhost:8000 видніється «Hello world!» ― значить все працює справно. Можна спробувати змінити вміст файлу /src/pages/index.js, щоб перевірити hot-reload.

Структура сторінок і роутинг

Щоб створити сторінку в Gatsby, досить просто помістити новий файл в папку /src/pages, та його буде скомпільовано в окрему HTML-сторінку. Важливо зауважити, що URL до цієї сторінки буде відповідати фактичному шляху з назвою. Наприклад, додамо ще кілька сторінок:

src
└── pages
    ├── about.js
    ├── index.js
    └── tutorial
        ├── part-four.js
        ├── part-one.js
        ├── part-three.js
        ├── part-two.js
        └── part-zero.js

Контент поки не важливий, тому можна використовувати будь-який текст задля того, щоб розрізняти сторінки:

import React from "react";

export default () => <div>Welcome to tutorial/part-one</div>;

Перевіряємо в браузері:

Ось таким чином можна, структуруючи файли, водночас вирішувати питання роутингу.

Також існує спеціальний createPage API, за допомогою якого можна більш гнучко керувати шляхами і назвами сторінок, але для роботи з ним нам знадобиться розуміння роботи даних в Gatsby, тому розглянемо його трохи далі в статті.

Зв’яжемо створені сторінки за допомогою посилань ― для цього скористаємося компонентом <Link /> з пакета Gatsby, який створений спеціально для внутрішньої навігації. Для всіх зовнішніх посилань слід використовувати звичайний <a> тег.

/src/pages/index.js

import React from "react";
import { Link } from "gatsby";

export default () => (
  <div>
    <ul>
      <li>
        <Link to="/about">about</Link>
      </li>
      <li>
        <Link to="/tutorial/part-zero">Part #0</Link>
      </li>
      <li>
        <Link to="/tutorial/part-one">Part #1</Link>
      </li>
      <li>
        <Link to="/tutorial/part-two">Part #2</Link>
      </li>
      <li>
        <Link to="/tutorial/part-three">Part #3</Link>
      </li>
      <li>
        <Link to="/tutorial/part-four">Part #4</Link>
      </li>
    </ul>
  </div>
);

<Link> під капотом має дуже хитрий механізм щодо оптимізації завантаження сторінок і тому використовується замість <a> для навігації по сайту. Детальніше можна почитати тут.

Сторінки створені, посилання додані. Схоже, що з навігацією закінчили.

Компоненти, шаблони та їх взаємодія

Як відомо, в будь-якому проекті завжди є елементи, що повторюються. Для веб-сайтів це хедер, футер, навігаційна панель. Також сторінки, незалежно від контенту, будуються за заданою структурою. Так як Gatsby — це компілятор для React , тут використовується той самий компонентний підхід для вирішення цих проблем.

Створимо компоненти для хедера та навігаційної панелі:

/src/components/header.js

import React from "react";
import { Link } from "gatsby";

/**
 * Зверніть увагу на те, що зображення для логотипу
 * імпортується так само, як і в звичайному React-проекті.
 * Це тимчасове і не оптимальне рішення, тому що картинка
 * поставляється "як є". Трохи далі ми розглянемо
 * як це робити "правильно" використовуючи GraphQL і gatsby-плагіни
 */
import logoSrc from "../images/logo.png";

export default () => (
  <header>
    <Link to="/">
      <img src={logoSrc} alt="logo" width="60px" height="60px" />
    </Link>
    That is header
  </header>
);

/src/components/sidebar.js

import React from "react";
import { Link } from "gatsby";

export default () => (
  <div>
    <ul>
      <li>
        <Link to="/about">about</Link>
      </li>
      <li>
        <Link to="/tutorial/part-zero">Part #0</Link>
      </li>
      <li>
        <Link to="/tutorial/part-one">Part #1</Link>
      </li>
      <li>
        <Link to="/tutorial/part-two">Part #2</Link>
      </li>
      <li>
        <Link to="/tutorial/part-three">Part #3</Link>
      </li>
      <li>
        <Link to="/tutorial/part-four">Part #4</Link>
      </li>
    </ul>
  </div>
);

і додамо їх в /src/pages/index.js

import React from "react";

import Header from "../components/header";
import Sidebar from "../components/sidebar";

export default () => (
  <div>
    <Header />
    <Sidebar />
    <h1>Index page</h1>
  </div>
);

Перевіряємо:

Все працює, але нам потрібно імпортувати Header і Sidebar на кожну сторінку окремо, що не дуже то й зручно. Щоб вирішити це питання, досить створити layout-компонент та огорнути ним кожну сторінку.

Gatsby layout == React container
так-так, саме неточна рівність, тому що це «майже» одне і те саме_

/src/components/layout.js

import React from "react";

import Header from "./header";
import Sidebar from "./sidebar";

export default ({ children }) => (
  <>
    <Header />
    <div
      style={{ margin: `0 auto`, maxWidth: 650, backgroundColor: `#eeeeee` }}
    >
      <Sidebar />
      {children}
    </div>
  </>
);

/src/pages/index.js (і всі інші сторінки)

import React from "react";

import Layout from "../components/layout";

export default () => (
  <Layout>
    <h1>Index page</h1>
  </Layout>
);

Готово, дивимося в браузер:

Чому в проекті всі назви файлів з маленької літери? Для початку визначимося, що naming convention для React виходить з того, що «кожен файл — це клас, а клас завжди називається з великої літери». В Gatsby файли, як і раніше, містять класи, але є одне «але»: «кожен файл є потенційною сторінкою, а його назва ― URL до цієї сторінки». Ком’юніті прийшло до висновку про те, що посилання виду http://domain.com/User/Settings ― це не comme-il-fautі, і затвердило kebab-case для назв файлів.

Структура файлів
src
├── components
│   ├── header.js
│   ├── layout.js
│   └── sidebar.js
├── images
│   └── logo.png
└── pages
    ├── about.js
    ├── index.js
    └── tutorial
        ├── part-eight.js
        ├── part-five.js
        ├── part-four.js
        ├── part-one.js
        ├── part-seven.js
        ├── part-six.js
        ├── part-three.js
        ├── part-two.js
        └── part-zero.js

Робота з даними

Тепер, коли структура сайту готова, можна переходити до наповнення контентом. Класичний «хардкод» підхід не влаштовував творців JAM-стеку, так само, як і «рендерити контент з AJAX-запитів». Тому вони запропонували заповнювати сайти контентом під час компіляції. У випадку з Gatsby за це відповідає GraphQL, який дозволяє зручно працювати з потоками даних з будь-яких джерел.

Розповісти про GraphQL в двох словах неможливо, тому бажано вивчити його самостійно або почекати моєї наступної статті. Детальніше про роботу з GraphQL можна почитати тут.

Для роботи з GraphQL, з другої версії, в пакеті gatsby є компонент StaticQuery, який може використовуватися як на сторінках, так і в простих компонентах, і в цьому його головна відмінність від його попередника ― page query. Поки що наш сайт не з’єднаний з якимись джерелами даних, тому спробуємо вивести метадані сторінок, для прикладу, а потім перейдемо до більш складних речей.

Щоб побудувати query, потрібно відкрити localhost:8000/___graphql і, користуючись бічною панеллю з документацією, знайти доступні дані про сайт. І не забудьте про автодоповнення.

/src/components/sidebar.js

import React from "react";
import { Link, StaticQuery, graphql } from "gatsby";

export default () => (
  <StaticQuery
    query={graphql`
      {
        allSitePage {
          edges {
            node {
              id
              path
            }
          }
        }
      }
    `}
    render={({ allSitePage: { edges } }) => (
      <ul>
        {edges.map(({ node: { id, path } }) => (
          <li key={id}>
            <Link to={path}>{id}</Link>
          </li>
        ))}
      </ul>
    )}
  />
);

Тепер ми, використовуючи query, отримуємо дані про сторінки, які рендеримо в панелі навігації. Більше не потрібно переживати з приводу того, що посилання не буде відповідати назві, тому що всі дані збираються автоматично.

По факту це всі дані, які можуть бути на нашому сайті без використання сторонніх плагінів і без старого доброго «хардкоду», тому ми плавно переходимо в наступну тему нашої статті ― плагіни.

Плагіни

За своєю суттю Gatsby — це компілятор з купою фішок, якими якраз і є плагіни. За допомогою них можна налаштовувати обробку тих чи інших файлів, типів даних і різних форматів.

Створимо на кореневому рівні додатку файл /gatsby-config.js<|em>, який відповідає за конфігурацію компілятора в цілому, і спробуємо налаштувати перший плагін для роботи з файлами.

Встановлення плагіну:

yarn add gatsby-source-filesystem

Конфігурація у файлі /gatsby-config.js:

module.exports = {
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images/`,
      }
    }
  ],
}

Детальніше про файл вище
/**
 * gatsby-config.js це файл, який повинен
 * за замовчуванням експортувати об'єкт JS
 * з конфігурацією для компілятора
 */
module.exports = {
  /**
   * поле 'plugins' описує pipeline процесу компіляції
   * та складається з набору плагінів
   */
  plugins: [
    /**
     * кожен плагін може бути вказаний у вигляді рядка
     * або у вигляді об'єкта для налаштування його опцій
     */
    `gatsby-example-plugin`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images/`,
      }
    }
  ],
}

Пам’ятайте ми говорили про «правильний» імпорт картинок в Gatsby?

/src/components/header.js

import React from "react";
import { Link, StaticQuery, graphql } from "gatsby";

export default () => (
  <StaticQuery
    query={graphql`
      {
        allFile(filter: { name: { eq: "logo" } }) {
          edges {
            node {
              publicURL
            }
          }
        }
      }
    `}
    render={({
      allFile: {
        edges: [
          {
            node: { publicURL }
          }
        ]
      }
    }) => (
      <header>
        <Link to="/">
          <img src={publicURL} alt="logo" width="60px" height="60px" />
        </Link>
        That is header
      </header>
    )}
  />
);

На сайті нічого не змінилося, але тепер картинка підставляється за допомогою GraphQL, замість простого webpack-імпорту. З першого погляду може здатися, що конструкції занадто складні і це лише додає проблем, але давайте не поспішати з висновками, бо справа в тих же самих плагінах. Наприклад, якби ми вирішили розміщувати на сайті тисячі фотографій, то нам в будь-якому випадку довелося б думати про оптимізацію завантаження всього контенту. Щоб не будувати свій lazy-load процесс з нуля, ми б просто додали gatsby-image плагін, який би оптимізував завантаження всіх картинок, що імпортуються за допомогою query.

Встановлення плагінів для стилізації:

yarn add gatsby-plugin-typography react-typography typography typography-theme-noriega node-sass gatsby-plugin-sass gatsby-plugin-styled-components styled-components babel-plugin-styled-components

gatsby-config.js

module.exports = {
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images/`
      }
    },
    // add style plugins below
    `gatsby-plugin-typography`,
    `gatsby-plugin-sass`,
    `gatsby-plugin-styled-components`
  ]
};

На офіційному сайті можна знайти плагін на будь-який смак.

Стилізація сайту

Почнемо стилізацію додатку, використовуючи різні підходи. У попередньому кроці ми вже встановили плагіни для роботи з SASS, styled-components та бібліотекою typography.js. При цьому важливо відзначити, що css.modules підтримуються «з коробки».

Почнемо роботу з глобальних стилів, які, як і інші речі, що відносяться до всього сайту, повинні бути налаштовані в файлі /gatsby-browser.js:

import "./src/styles/global.scss";

Детальніше про gatsby-browser.js.

/src/styles/global.scss

body {
  background-color: lavenderblush;
}

З різних причин тенденції останніх років схиляються в бік «CSS in JS» підходу, тому не варто зловживати глобальними стилями і краще обмежитися зазначенням шрифту і глобальних класів. У цьому конкретному проекті планується використання Typography.js для цих цілей, тому глобальні стилі залишаться порожніми.

Ви вже могли помітити зміни зовнішнього вигляду сайту після додавання gatsby-plugin-typography в конфігурацію. Це тому, що був застосований його пресет за замовчуванням, а зараз ми сконфігуруємо його під себе.

/src/utils/typography.js

import Typography from "typography";
import theme from "typography-theme-noriega";

const typography = new Typography(theme);

export default typography;

Можна вибрати будь-який інший пресет зі списку або створити свій власний, використовуючи API пакету (приклад конфігурації офіційного сайту Gatsby).

/gatsby-config.js

module.exports = {
  plugins: [
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images/`
      }
    },
    {
      resolve: `gatsby-plugin-typography`,
      options: {
        pathToConfigModule: `src/utils/typography`
      }
    },
    `gatsby-plugin-sass`,
    `gatsby-plugin-styled-components`
  ]
};

І в залежності від обраного пресету глобальний стиль сайту буде змінений. Яким підходом налаштовувати глобальні стилі, вирішуйте самі, бо відмінностей з технічної точки зору немає і тому це питання смаку. А ми переходимо до стилізації компонентів, використовуючи styled-components.

Додамо файл з глобальними змінними /src/utils/vars.js

export const colors = {
  main: `#663399`,
  second: `#fbfafc`,
  main50: `rgba(102, 51, 153, 0.5)`,
  second50: `rgba(251, 250, 252, 0.5)`,
  textMain: `#000000`,
  textSecond: `#ffffff`,
  textBody: `#222222`
};

/src/components/header.js
import React from "react";
import { Link, StaticQuery, graphql } from "gatsby";
import styled from "styled-components";

import { colors } from "../utils/vars";

const Header = styled.header`
  width: 100%;
  height: 3em;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background-color: ${colors.main};
  color: ${colors.textSecond};
  padding: 0.5em;
`;

const Logo = styled.img`
  border-radius: 50%;
  height: 100%;
`;
const logoLink = `height: 100%;`;

export default () => (
  <StaticQuery
    query={graphql`
      {
        allFile(filter: { name: { eq: "logo" } }) {
          edges {
            node {
              publicURL
            }
          }
        }
      }
    `}
    render={({
      allFile: {
        edges: [
          {
            node: { publicURL }
          }
        ]
      }
    }) => (
      <Header>
        That is header
        <Link to="/" css={logoLink}>
          <Logo src={publicURL} alt="logo" />
        </Link>
      </Header>
    )}
  />
);

/src/components/sidebar.js
import React from "react"
import { Link, StaticQuery, graphql } from "gatsby"
import styled from "styled-components"

import { colors } from "../utils/vars"

const Sidebar = styled.section`
  position: fixed;
  left: 0;
  width: 20%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  background-color: ${colors.second};
  color: ${colors.textMain};
`

const navItem = `
  display: flex;
  align-items: center;
  margin: 0 1em 0 2em;
  padding: 0.5em 0;
  border-bottom: 0.05em solid ${colors.mainHalf};
  postion: relative;
  color: ${colors.textBody};
  text-decoration: none;

  &:before {
    content: '';
    transition: 0.5s;
    width: 0.5em;
    height: 0.5em;
    position: absolute;
    left: 0.8em;
    border-radius: 50%;
    display: block;
    background-color: ${colors.main};
    transform: scale(0);
  }

  &:last-child {
    border-bottom: none;
  }

  &:hover {
    &:before {
      transform: scale(1);
    }
  }
`

export default () => (
  <StaticQuery
    query={graphql`
      {
        allSitePage {
          edges {
            node {
              id,
              path
            }
          }
        }
      }
    `}
    render={({
      allSitePage: {
        edges
      }
    }) => (
      <Sidebar>
        {
          edges.map(({
            node: {
              id,
              path
            }
          }) => (
            <Link to={path} key={id} css={navItem} >{id}</Link>
          ))
        }
      </Sidebar>
    )}
  />

)

Вже існуючі елементи стилізовані, і настав час зв’язати контент з Contentful, підключити Markdown-плагін і згенерувати сторінки, використовуючи createPages API.

Детальніше про те, як зв’язати Gatsby і Contentful, читайте в попередній статті.

Структура моїх даних з Contentful
[
  {
    "id": "title",
    "type": "Symbol"
  },
  {
    "id": "content",
    "type": "Text",
  },
  {
    "id": "link",
    "type": "Symbol",
  },
  {
    "id": "orderNumber",
    "type": "Integer",
  }
]

Встановлення пакетів:

yarn add dotenv gatsby-source-contentful gatsby-transformer-remark

/gatsby-config.js

if (process.env.NODE_ENV === "development") {
  require("dotenv").config();
}

module.exports = {
  plugins: [
    `gatsby-transformer-remark`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images/`,
      }
    },
    {
      resolve: `gatsby-plugin-typography`,
      options: {
        pathToConfigModule: `src/utils/typography`,
      },
    },
    {
      resolve: `gatsby-source-contentful`,
      options: {
        spaceId: process.env.CONTENTFUL_SPACE_ID,
        accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
      },
    },
    `gatsby-plugin-sass`,
    `gatsby-plugin-styled-components`,
  ],
}

Видаляємо папку /src/pages з усіма файлами всередині і створюємо новий файл для керування вузлами в Gatsby:

/gatsby-node.js

const path = require(`path`);

/**
 * Експортована функція, яка перезапише існуючу за замовчуванням,
 * та буде викликана для генерації сторінок
 */
exports.createPages = ({ graphql, actions }) => {
  /**
   * Отримуємо метод для створення сторінки з екшенів,
   * щоб уникнути зайвих імпортів і зберігати контекст
   * сторінки і функції
   */
  const { createPage } = actions;
  return graphql(`
    {
      allContentfulArticle {
        edges {
          node {
            title
            link
            content {
              childMarkdownRemark {
                html
              }
            }
          }
        }
      }
    }
  `).then(({ data: { allContentfulArticle: { edges } } }) => {
    /**
     * Для кожного елемента з відповіді
     * викликаємо createPage () функцію і передаємо
     * всередину дані за допомогою контексту
     */
    edges.forEach(({ node }) => {
      createPage({
        path: node.link,
        component: path.resolve(`./src/templates/index.js`),
        context: {
          slug: node.link
        }
      });
    });
  });
};

Детальніше про gatsby-node.js.

Створюємо template-файл, який буде основою для сторінок, які генеруються/src/templates/index.js

import React from "react";
import { graphql } from "gatsby";
import Layout from "../components/layout";

export default ({
  data: {
    allContentfulArticle: {
      edges: [
        {
          node: {
            content: {
              childMarkdownRemark: { html }
            }
          }
        }
      ]
    }
  }
}) => {
  return (
    <Layout>
      <div dangerouslySetInnerHTML={{ __html: html }} />
    </Layout>
  );
};

export const query = graphql`
  query($slug: String!) {
    allContentfulArticle(filter: { link: { eq: $slug } }) {
      edges {
        node {
          title
          link
          content {
            childMarkdownRemark {
              html
            }
          }
        }
      }
    }
  }
`;

Чому тут не використовується <StaticQuery /> компонент? Вся справа в тому, що він не підтримує змінні для побудови запиту, а нам потрібно використовувати змінну $slug з контексту сторінки.

Оновлюємо логіку в навігаційній панелі
import React from "react";
import { Link, StaticQuery, graphql } from "gatsby";
import styled from "styled-components";

import { colors } from "../utils/vars";

const Sidebar = styled.section`
  position: fixed;
  left: 0;
  width: 20%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  background-color: ${colors.second};
  color: ${colors.textMain};
`;

const navItem = `
  display: flex;
  align-items: center;
  margin: 0 1em 0 2em;
  padding: 0.5em 0;
  border-bottom: 0.05em solid ${colors.main50};
  postion: relative;
  color: ${colors.textBody};
  text-decoration: none;

  &:before {
    content: '';
    transition: 0.5s;
    width: 0.5em;
    height: 0.5em;
    position: absolute;
    left: 0.8em;
    border-radius: 50%;
    display: block;
    background-color: ${colors.main};
    transform: scale(0);
  }

  &:last-child {
    border-bottom: none;
  }

  &:hover {
    &:before {
      transform: scale(1);
    }
  }
`;

export default () => (
  <StaticQuery
    query={graphql`
      {
        allContentfulArticle(sort: { order: ASC, fields: orderNumber }) {
          edges {
            node {
              title
              link
              orderNumber
            }
          }
        }
      }
    `}
    render={({ allContentfulArticle: { edges } }) => (
      <Sidebar>
        {edges.map(({ node: { title, link, orderNumber } }) => (
          <Link to={link} key={link} css={navItem}>
            {orderNumber}. {title}
          </Link>
        ))}
      </Sidebar>
    )}
  />
);

SEO-оптимізація з використанням react-helmet

З технічної точки зору сайт можна вважати готовим, тому давайте попрацюємо з його мета-даними. Для цього нам знадобляться такі плагіни:

yarn add gatsby-plugin-react-helmet react-helmet

react-helmet генерує <head> ... </ head> для HTML сторінок і в зв’язці з Gatsby рендерингом є потужним і зручним інструментом для роботи з SEO.

/src/templates/index.js

import React from "react";
import { graphql } from "gatsby";
import { Helmet } from "react-helmet";

import Layout from "../components/layout";

export default ({
  data: {
    allContentfulArticle: {
      edges: [
        {
          node: {
            title,
            content: {
              childMarkdownRemark: { html }
            }
          }
        }
      ]
    }
  }
}) => {
  return (
    <Layout>
      <Helmet>
        <meta charSet="utf-8" />
        <title>{title}</title>
      </Helmet>
      <div dangerouslySetInnerHTML={{ __html: html }} />
    </Layout>
  );
};

export const query = graphql`
  query($slug: String!) {
    allContentfulArticle(filter: { link: { eq: $slug } }) {
      edges {
        node {
          title
          link
          content {
            childMarkdownRemark {
              html
            }
          }
        }
      }
    }
  }
`;

Тепер title сайту буде завжди збігатися з назвою статті, що буде істотно впливати на видачу сайту в результатах пошуку, конкретно з цього питання. Сюди ж можна легко додати <meta name="description" content="Опис статті"> з описом кожної статті окремо, надаючи цим можливість користувачеві, ще на сторінці пошуку, зрозуміти, про що йде мова в статті. І взагалі всі можливості SEO тепер доступні, і ними можна керувати з одного місця.

Налаштування PWA

Gatsby розроблений, щоб забезпечити першокласну продуктивність «з коробки». Він бере на себе питання щодо розділення і мінімізації коду, а також оптимізації у вигляді попереднього завантаження у фоновому режимі, обробки зображень і ін. Отже, створюваний вами сайт має високу продуктивність без будь-яких додаткових налаштувань. Ці функції продуктивності є важливою частиною підтримки прогресивного підходу до веб-додатків.

Але крім усього, перерахованого вище, існують три базові критерії для сайту, які визначають його як PWA:

Перший пункт не може бути вирішений силами Gatsby, оскільки домен, хостинг і протокол — це питання деплойменту, і ніяк не розробки. Але можу порадити Netlify, який легко вирішує проблему з https.

Переходимо до інших пунктів. Для цього встановимо два плагіни:

yarn add gatsby-plugin-manifest gatsby-plugin-offline

і налаштуємо їх /src/gatsby-config.js

if (process.env.NODE_ENV === "development") {
  require("dotenv").config();
}

module.exports = {
  plugins: [
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `GatsbyJS translated tutorial`,
        short_name: `GatsbyJS tutorial`,
        start_url: `/`,
        background_color: `#f7f0eb`,
        theme_color: `#a2466c`,
        display: `standalone`,
        icon: `public/favicon.ico`,
        include_favicon: true
      }
    },
    `gatsby-plugin-offline`,
    `gatsby-transformer-remark`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images/`
      }
    },
    {
      resolve: `gatsby-plugin-typography`,
      options: {
        pathToConfigModule: `src/utils/typography`
      }
    },
    {
      resolve: `gatsby-source-contentful`,
      options: {
        spaceId: process.env.CONTENTFUL_SPACE_ID,
        accessToken: process.env.CONTENTFUL_ACCESS_TOKEN
      }
    },
    `gatsby-plugin-sass`,
    `gatsby-plugin-styled-components`,
    `gatsby-plugin-react-helmet`
  ]
};

Ви можете налаштувати свій маніфест, використовуючи документацію, а також кастомізувати стратегію service-workers, перезаписавши налаштування плагіну.

Ніяких змін в режимі розробки ви не помітите, але сайт вже відповідає останнім вимогам світу web. І коли він буде розміщений на https:// домені, йому не буде рівних.

Висновок

Кілька років тому, коли я вперше зіткнувся з проблемами виведення в інтернет React-додатку, його підтримки і оновлення контенту, я і не міг уявити, що на ринку вже існував JAM-stack підхід, який спрощує всі ці процеси. І зараз я не перестаю дивуватися його простоті. Gatsby вирішує більшість питань, які впливають на продуктивність сайту просто «з коробки». А якщо ще трохи розібравшись в тонкощах, налаштувати його під свої потреби, то можна отримати 100% показники за всіма пунктами в Lighthouse, чим суттєво вплинути на видачу сайту в пошукових системах (принаймні в Google).

Репозиторій з проектом

Наостанок

Як ви могли помітити, розглянутий в статті проект копіює основний сайт з документацією Gatsby.js. Це неспроста, тому що я замахнувся перекласти хоча би вступний туторіал російською та українською мовами, щоб популяризувати цей стек в Україні та СНД. Подивитися на поточну версію можна тут.

Читайте також мою попередню статтю про те, як створити і опублікувати особистий блог, використовуючи JAM-stack.

LinkedIn

22 комментария

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.
інструмент для створення складних веб-сайтів

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

Nuxt.js (а для любителей реакта на крайняк Next.js) — могут то же что и сабж, но и вживую рендерить умеют. Вот это инструменты как раз для сложных сайтов.

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

По поводу Nuxt.js, не вижу смысла сравнивать SSR и JAM-stack, потому что со стороны перфоманса второй явно побеждает.

По поводу Nuxt.js, не вижу смысла сравнивать SSR и JAM-stack,

сравнивать есть смысл, т.к. сравнивается SSR + JAM-stack (Nuxt.js) и голый JAM-stack.

потому что со стороны перфоманса второй явно побеждает.

Если все таки говорить о Nuxt.js как о SSR, то на практике — никто явно не побеждает. Потому что в сложных проектах есть прокси-кеш перед рендерером. Так что тут разница разве что в инфраструктуре: JAM-Stack легче деплоить на какой-то бесплатный хостинг чтоб вести свой блог или сайт документации обновляя его раз в месяц.
Даже если забыть о кешировании, то разница между пререндером и рендером вживую — 50мс на первоначальный ответ сервера, в простом случае. Это пара процентов от времени загрузки страницы.

Тут вы правы, я не работал с Nuxt.js, и поэтому не могу точно утверждать, но вроде как документация говорит что Nuxt.js это и есть JAM-stack? Иначе говоря это Gatsby для Vue.js

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

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

Спасибо, интересная статья. В блоке о SEO написали только про мета-теги. Как на счет видимости контента поисковиками?

С точки зрения разработки кроме мета-тегов и валидной верстки мы больше никак не влияем на SEO.
По поводу того как поисковики «видят» контент, то ответ вытекает из того что такое JAM-stack и в чем его «особенность» :

Страницы поставляются с уже заполненным контентом и когда crawler переходит по адресу: gatsbyjs-tutorial.alexandrtovmach.com , то он сразу получает страницу со всем контентом на ней:
— никакого рендера, как в React/Angular/Vue
— страницы отдаются быстрее по сравнению с server-side rendering подходом, потому как не нужно ничего ждать, страница уже есть и она просто отдается как готовый файл.

С точки зрения разработки это максимум того что можно сделать в плане SEO на сегодняшний день. Поправьте меня если я ошибаюсь.

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

Спасибо за статью!

Но есть много непонятных моментов. Посмотрел в браузере в DevTools исходники того что есть (gatsbyjs-tutorial.alexandrtovmach.com) и был немного удивлён тому что в итоге получилось на выходе (надпись «Suport this view will coming» — вертикальный монитор, на горизонтальном мониторе в окне не на весь экран тоже пустая страница, по сути много секса ради ничего):

1. Огромное количество CSS стилей в HEAD
2. JS скрипты подключаются в HEAD
3. in-line CSS
4. JS в HTML коде
5. JS скрипты с тегом async — вы это серьёзно в эпоху http2?

Не сильно удивлюсь если там ещё и in-line JS будет прицепом... Может проще всё то же самое делать классическим вариантом с использованием легковесных библиотек вместо тяжелых непонятных фреймворков и их непонятной магии: html — отдельно, css — отдельно, js — отдельно, никакого in-line кода и строгие Content-Security-Policy?

P.S. Судя по тому что оно не везде и не всегда работает оно вообще живое или мёртвенькое?..

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

Теперь по пунктам:
1. Все стили добаляются для поддержки различных типов маркдауна, но по поводу огромного количества смотря с чем сравнивать.
2. Да, и в чем собственно проблема, если они асинхронны? Изначально <head> для этого и задумывался, а размещение скриптов в конце <body> это хак, ставший общепризнаным решением для существующих проблем. В гетсби этих проблем нет, поэтому взятки-гладки)
3. Тема для холивара, не более=) Конечный билд должен быть оптимизирован, и не обязательно читабелен, в исходном коде можно работать как удобно.
4. Оптимизация билда, причем вполне себя оправдывающая. Опять же повторюсь, в исходном коде вы пишите как вам удобно.
5. Да, не привычно, но это нормальный подход.

Как вывод могу сказать что искать разделение сущностей в конечном оптимизированном билде ― слегка странно + каждый из пунктов является фичей для оптимизации.

Может проще всё то же самое делать классическим вариантом

Бесспорно, каждому делу свой инструмент, и я не призываю для написания лендинга или трехстраничного сайта-визитки использовать Gatsby, но вот для реализации блога, с поддержкой markdown, lazy-load и кешированием, всё таки лучше посмотреть в сторону фреймворков, в которых это реализовывается в пару строк.

Вы не подумайте, я не придираюсь, просто делаю обзорную попытку выбраться из сложных SPA в сторону чего-то более публичного и повседневного, поэтому возникает ряд вопросов и по оптимизации в том числе.
1. По CSS в head. У вас куча лишнего и избыточного, например весь padding можно уместить в «padding: 0 0 0 0;» вместо описания каждого их 4х элементов отдельно (paddint-top, padding-right, padding-bottom, padding-left), тем более что оно везде повторяется ну и т.д.
2. Загрузка JS в head по-идее вывалит браузер в белый экран если JS распарсится с ошибкой, печаль и боль, поэтому рекомендуют весь JS закидывать в самый конец body, тем более такой подход не блокирует рендеринг страницы до загрузки и парсинга всего JS, раз уж вы ссылаетесь на оценку в Litehouse и скорость отображения страницы.
3. в холивар, не суть, можно закрыть глаза, но это не удобно, т.к. in-line CSS имеет наивысший приоритет после !important.
4. Таки критично
5. не мешает, но смотрится как иконки в новом авто.

1. Если бы писал руками, да, но опять же, если бы я писал руками свои стили для отображения всех используемых пакетов, то я сомневаюсь что я бы сделал блог за 2 дня
2. Интересно бы увидеть информацию по данному поводу. Загрузка скрипта блокирует построение DOM, и после того как скрипт загружен ил выпал с ошибкой DOM продолжает строится. Но теперь добавим async, и всё =)
3. Эти инлайн-стили делает компилятор
4. ....
5. Каждому своё, главное не путать то как данные загружаются по сети http2 и то как их обрабатывает браузер async

Да, почитал доки, на счёт async вы таки правы.

В инспекторе кода не нашел ни одного «in-line CSS». Вы точно видели его на этом сайте?

Например такое:
<div style="outline:none" ... >
подойдёт? Я как-бы в курсе что такое in-line CSS и in-line JS

Ага, тоже нашел. Ну тут же сразу видно, что это дело «рук» человека и тулза тут не приче. Я бы к таком не стал придераться. Везде где разработчик не поленился использовать styled-components нормальные сгенерированные имена классов.

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

Тогда Вам стоит использовать проверенные Вами и временем фреймворки. Как по мне.

Как говорится это не «баг» это «фича» =)

Этот инлайн стиль добавляется в Gatsby чтобы соответстовать требованиям a11y и помогать screen reader’ам с чтением страницы:
reach.tech/router/accessibility

Вот все и стало на свои места. Тулза борится за людей с ограничеными возможностями.

Подписаться на комментарии