- Установка
- Graphql
- Контент
- Мультиязычность и SEO
- gatsby-ssr
- Webpack
- Картинки вне static
- Dark mode
- Подсветка кода
- Комментарии
- MDX
- Публикация
- Послесловие
На выходных захотелось поэкспериментиро вать с блогом. Возможности vuepress исследованы. Душа просит новизны. Под руку попался gatsby. Ну, что ж, попробуем сделать мультиязычный сайт и поиграть с graphql, который поставляется как вариант по-умолчанию для передачи данных.
Установка
Любимый для подобных вещей npx
спешит на помощь. Выбираем минимальный starter-шаблон, чтобы
всё сделать по-своему с нуля. Используемая версия gatsby: 4.
$ npx gatsby new gatsby-blog https://github.com/gatsbyjs/gatsby-starter-hello-world
$ cd gatsby-blog
$ npm run develop
Файловая структура представлена ниже. Можно не воспроизводить её как есть, она лишь иллюстрирует откуда берутся те или иные файлы и помогает ориентиров аться в дальнейших кусках кода.
.
├── content
│ ├── en
│ │ └── frontend
│ └── ru
│ ├── frontend
│ └── backend
├── gatsby-browser.js
├── gatsby-config.js
├── gatsby-node.js
├── gatsby-ssr.js
├── src
│ ├── assets
│ │ ├── images
│ │ │ └── great.jpg
│ │ └── styles
│ │ └── base.css
│ ├── components
│ │ ├── layout.js
│ │ └── theme-switcher.js
│ ├── pages
│ │ ├── 404.js
│ │ └── index.js
│ ├── templates
│ │ └── post-template.js
│ └── translations
│ ├── en.json
│ ├── ru.json
│ └── index.js
└── static
├── favicon.ico
├── sitemap.xml
└── robots.txt
Из названий, в общем-то, всё понятно. Единственное, что стоило бы уточнить: данные будут представлять собой markdown-файлы. Это наша база данных. Типичная для генератора статики, но не являющаяся единственным вариантом в случае с gatsby.
Graphql
После получения копии gatsby, можно сразу же запустить его в режиме разработки и поиграть
с graphql. Чтобы получить что-то полезное, сначала надо это полезное написать. Хотя бы
задать глобальные данные для сайта. Сделать это можно в gatsby-config.js
.
// gatsby-config.js
module.exports = {
siteMetadata: {
title: "Great gatsby multi-language blog",
},
}
Открыть браузер по адресу http://localhost:8000/___graphql и написать запрос:
{
site {
siteMetadata {
title
}
}
}
В файле конфигурации можно прописать названия и пути к каким-либо страницам и вывести
динамическое меню. Только помните: если запросы используются в компоненте, получайте их
через useStaticQuery
. Но обо всём по порядку.
Надеюсь, вы получили в ответ свой заголовок. Пора отправляться дальше.
Контент
Главная вещь, ради кот орой мы тут собрались: данные. Этот раздел самый большой, поэтому приготовьте чаю.
Данные
Перво-наперво пишем заметочку. Лучше несколько.
По slug
определим путь к заметке. Category
для тех, кто хочет разбивать страницы по категориям.
Конечно, дата, в формате год-месяц-день. Остальное должно быть понятно.
<!--content/ru/frontend/gatsby/gatsby.mdx-->
---
h1: Заголовок поста
title: Title для браузера
description: Описание для поисковых роботов
date: 2020-04-06
category: frontend
slug: gatsby
---
Мой контент
![alt](./tree.jpg)
Структура директории в формате локаль -> категория -> запись
:
.
├── content
│ ├── en
│ │ └── frontend
│ └── ru
│ ├── frontend
│ │ ├── gatsby
│ │ │ ├── gatsby.jpg
│ │ │ └── gatsby.mdx
Инструменты
Установка пакетов для работы с форматом mdx (в отличие от обычного markdown эта штука позволит использовать компоненты прямо в md-файлах. Ну не круто ли?).
$ npm i gatsby-plugin-mdx @mdx-js/mdx @mdx-js/react
Пакет «расшаривания» директории, где лежит контент, чтобы его содержимое было видно в graphql.
$ npm i gatsby-source-filesystem
И пакеты, позволяющие запросто подключать картинки и хранить их не где-то в static
,
а рядом с файлом-заметкой.
$ npm i gatsby-plugin-sharp gatsby-remark-images
Скормим их в файлу конфигурации:
// gatsby-config.js
module.exports = {
// ...
plugins: [
"gatsby-plugin-sharp",
{
resolve: "gatsby-source-filesystem",
options: {
name: "content",
// шарить директорию content
path: `${__dirname}/content/`,
},
},
{
resolve: `gatsby-plugin-mdx`,
options: {
extensions: [`.mdx`, `.md`],
gatsbyRemarkPlugins: [
{
resolve: `gatsby-remark-images`,
options: {
maxWidth: 1200,
},
},
],
},
},
],
}
Данные подготовлены, пакеты установлены. Дальше следует выяснить как работает механизм
передачи данных. Для каждого markdown-файла создаётся страница (gatsby-node.js
).
Страница должна иметь шаблон (post-template.js
). В шаблоне можно вывести что угодно.
Поехали.
Создание страниц
// gatsby-node.js
const path = require("path")
// createPages даёт доступ к graphql и некоторым методам gatsby (actions)
exports.createPages = async ({ actions, graphql }) => {
const { createPage } = actions
// получить все markdown-записи
const {
data: {
allMdx: { edges: posts },
},
} = await graphql(`
{
allMdx {
edges {
node {
frontmatter {
slug
category
}
}
}
}
}
`)
// для каждой записи создать страницу
posts.forEach(({ node }) => {
const { slug, category } = node.frontmatter
return createPage({
// путь к странице
path: `${category}/${slug}`,
// шаблон страницы
component: require.resolve("./src/templates/post-template.js"),
// контекст, который попадёт в шаблон
// может быть использован для дальнейших манипуляций с данными
context: { slug },
})
})
}
Шаблон
В шаблоне запрашиваем любые данные.
// src/templates/post-template.js
import React from "react"
import { graphql } from "gatsby"
import { MDXRenderer } from "gatsby-plugin-mdx"
// динамический slug берётся из переданного на этапе создания страниц контекста
export const query = graphql`
query getPost($slug: String!) {
mdx(frontmatter: { slug: { eq: $slug } }) {
body
frontmatter {
h1
slug
}
}
}
`
// Отобразить контент помогает MDXRenderer
export default function PostTemplate({ data }) {
const { h1 } = data.mdx.frontmatter
const { body } = data.mdx
return (
<main>
<h1>{h1}</h1>
<MDXRenderer>{body}</MDXRenderer>
</main>
)
}
Всё! Новую запись можно увидеть по адресу http://localhost:8000/frontend/gatsby/
.
Это был самый трудоёмкий этап. Дальше можно вывести все записи на главной и перейти к пунктам
более простым и не менее полезным.
Записи на главной:
// pages/index.js
import React from "react"
import { graphql, useStaticQuery, Link } from "gatsby"
// получить все записи, отсортированные по дате
const getPosts = graphql`
query allPosts {
allMdx(sort: { frontmatter: {date: DESC} }) {
edges {
node {
frontmatter {
h1
slug
category
date(formatString: "MMMM Do, YYYY")
}
}
}
}
}
`
export default function HomePage() {
const response = useStaticQuery(getPosts)
const posts = response.allMdx.edges
return (
<main>
<h1>Posts</h1>
<ul>
{posts.map(({ node: { frontmatter: post } }, index) => (
<li key={index}>
<span>{post.date}</span><br/>
<Link to={`${post.category}/${post.slug}/`}>{post.h1}</Link>
</li>
))}
</ul>
</main>
)
}
Объект posts
можно фильтровать и сортировать как захочется прямо здесь. На производительность
это не повлияет. После сборки останется статичная страница с преобразованными заранее данными.
Мультиязычность и SEO
На мой взгляд хорошим подспорьем для переводов в обычных javascript-файлах,
таких как страницы (pages), может быть пакет react-intl
. Однако, есть одна проблема:
для gatsby его метод injectIntl
работать не будет: не предусмотрен для использования
в этом окружении. Но не беда, есть обёртка над react-intl
, её и установим.
$ npm i gatsby-plugin-intl
Файл конфигурации принимает следующий вид:
// gatsby-config.js
module.exports = {
// ...
plugins: [
// ...
{
resolve: "gatsby-plugin-intl",
options: {
path: `${__dirname}/src/translations`,
languages: ["en", "ru"],
defaultLanguage: "ru",
// автоматически перенаправлять на `/ru` или `/en` когда человек на главной `/`
// имейте ввиду: у Google Chrome всегда стоит `en-US`! экспериментируйте
redirect: false,
},
},
],
}
Создайте файлы с языковыми переводами, если не сделали этого раньше.
src
└── translations
├── en.json
├── ru.json
└── index.js
Переводы не должны иметь вложенность. Только flat. Это ограничение react-intl
.
Оно, конечно, обходится, но не станем заострять на этом внимание сейчас.
// src/translations/en.json
{
"home.h1": "Hello, welcome {user}",
"home.title": "Home title",
"home.description": "Home description"
}
Предпочитаю брать их все из одного места. Так удобнее.
// src/translations/index.js
import locale_en from "./en.json"
import locale_ru from "./ru.json"
export default {
en: locale_en,
ru: locale_ru,
}
В markdown-файлы добавить информацию о языке, это очень важные данные!
<!--content/ru/frontend/gatsby/gatsby.mdx-->
---
h1: Заголовок поста
lang: ru
<!--content/en/frontend/gatsby/gatsby.mdx-->
---
h1: Post heading
lang: en
Поскольку контентные страницы создаём самостоятельно, передавать контекст тоже надо самостоятельно.
// gatsby-node.js
exports.createPages = async ({ actions, graphql }) => {
// запрос тот же, только lang добавить
// ...
frontmatter {
lang
slug
category
}
// ...
// создать отдельные страницы для разных языков
posts.forEach(({ node }) => {
const { lang, slug, category } = node.frontmatter
return createPage({
path: `${lang}/${category}/${slug}`,
component: require.resolve("./src/templates/post-template.js"),
context: { lang, slug },
})
})
}
И lang
в шаблон, конечно, прокинуть. Надо же теперь распределять весь контент
по языковой принадлежности! Заодно правильно отформатируем даты для текущего языка.
// src/templates/post-template.js
export const query = graphql`
query getPost($slug: String!, $lang: String!) {
mdx(frontmatter: { slug: { eq: $slug }, lang: { eq: $lang } }) {
body
frontmatter {
h1
date(formatString: "MMMM Do, YYYY", locale: $lang)
}
}
}
`
Страницы в page
после установки плагина обзавелись новой переменной контекста — language
.
Теперь для каждой из них можно получить язык, установленный в браузере посетителя.
Фильтровать по этому признаку записи или добавлять seo-заголовки. Попробуем.
// src/pages/index.js
// ... добавить в запрос lang
frontmatter {
lang
h1
slug
category
}
// ...
export default function HomePage({ pageContext: { language } }) {
const response = useStaticQuery(getPosts)
// только записи с актуальной локалью
const posts = response.allMdx.edges.filter(
post => post.node.frontmatter.lang === language
)
return (
<main>
<ul>
{posts.map(({ node: { frontmatter: post } }, index) => (
<li key={index}>
<Link to={`/${post.lang}/${post.category}/${post.slug}/`}>
{post.h1}
</Link>
</li>
))}
</ul>
</main>
)
}
Осталось разобраться с локализацией самих js-страниц. На сцену снова выходит react-intl
.
Конечно, захочется правильно устанавливать html-атрибут lang
и писать в head
всякие
красивые мета-данные для поисковых роботов. В этом поможет layout.js
, куда будем складывать всё
это добро. Ставим react-helmet
и gatsby-plugin-react-helmet
:
$ npm i react-helmet gatsby-plugin-react-helmet
В gatsby.config.js
добавляем последний. Так мета-теги будут учитываться при генерации статики.
// gatsby.config.js
plugins: [
'gatsby-plugin-react-helmet',
]
Чтобы не страдать с импортом относительных путей, докрутим webpack.
// gatsby-node.js
exports.onCreateWebpackConfig = ({ actions }) => {
actions.setWebpackConfig({
resolve: {
modules: [path.resolve(__dirname, "src"), "node_modules"],
},
})
}
Дописываем в gatsby-config.js
глобальные title и description. Или не дописываем, а передаём
с конкретной страницы. Тогда и graphql-запрос не нужен. В общем, идём делать SEO.
// components/layout.js
import React from "react"
import { Helmet } from "react-helmet"
import { useStaticQuery, graphql } from "gatsby"
export default function Layout({
lang = "ru",
title = "",
description = "",
children,
}) {
const { site } = useStaticQuery(
graphql`
query {
site {
siteMetadata {
title
description
}
}
}
`
)
const metaDescription = description || site.siteMetadata.description
return (
<>
<Helmet
htmlAttributes={{ lang }}
title={title}
titleTemplate={`%s | ${site.siteMetadata.title}`}
meta={[
// по желанию добавить Open Graph для социальных сетей
{ name: "description", content: metaDescription },
]}
/>
<main>{children}</main>
</>
)
}
Допустим, pages/index.js
пуст (если нет, создаём любой другой, к примеру about.js
).
// pages/index.js
import React from "react"
import {
useIntl,
FormattedDate,
FormattedMessage,
FormattedNumber,
} from "gatsby-plugin-intl"
import Layout from "components/layout"
// получить language из контекста и вывести:
// правильно отформатированные дату, число и валюту, да просто переменную в тексте
// красота!
export default function HomePage({ pageContext: { language } }) {
const intl = useIntl()
return (
<Layout
lang={language}
title={intl.formatMessage({ id: "home.title" })}
description={intl.formatMessage({ id: "home.description" })}
>
<p>
<FormattedDate value={new Date()} /><br />
<FormattedNumber value={12000} style="currency" currency="USD" /><br />
<FormattedMessage id="home.h1" values={{ user: "Jack" }} />
</p>
</Layout>
)
}
На этом вопросы мультиязычности и SEO считаю закрытыми.
gatsby-ssr
Надо бы затронуть и этот файл тоже. Но для чего? Gatsby встраивает инлайновые стили на страницу. Не всем это придётся по вкусу по той причине, что такие стили по-умолчанию не кэшируются. Это поведение меняется здесь. На любителя.
// gatsby-ssr.js
export const onPreRenderHTML = ({ getHeadComponents, replaceHeadComponents }) => {
// для разработки оставить inline css
if (process.env.NODE_ENV !== "production") return
const headComponents = getHeadComponents()
headComponents.forEach(el => {
// для итоговой сборки сделать отдельный css-файл
if (el.type === "style") {
el.type = "link"
el.props["href"] = el.props["data-href"]
el.props["rel"] = "stylesheet"
el.props["type"] = "text/css"
delete el.props["data-href"]
delete el.props["dangerouslySetInnerHTML"]
}
})
replaceHeadComponents(headComponents)
}
Webpack
Небольшой пример тюнинга конфигурации webpack был рассмотрен в предыдущем разделе.
// gatsby-node.js
exports.onCreateWebpackConfig = ({ getConfig, actions, plugins }) => {
actions.setWebpackConfig({
// отключить source-map в итоговой сборке
devtool: getConfig().mode === "production" ? false : "source-map",
resolve: {
modules: [path.resolve(__dirname, "src"), "node_modules"],
},
// по желанию вырубить react-dev-tools
plugins: [
plugins.define({
'__REACT_DEVTOOLS_GLOBAL_HOOK__': `({ isDisabled: true })`
})
],
})
}
Здесь упомяну лишь о том, как ставить свои плагины. На самом деле нет ничего проще.
Ставим что хотим + обязательно babel-preset-gatsby
.
$ npm i --save-dev babel-preset-gatsby @babel/plugin-proposal-optional-chaining
Создаём свой .babelrc
в корне проекта.
{
"plugins": [
"@babel/plugin-proposal-optional-chaining"
],
"presets": [
[
"babel-preset-gatsby",
{
"targets": {
"browsers": [
">0.5%",
"not dead"
]
}
}
]
]
}
Всё. Уже можно использовать plugin-proposal-optional-chaining
.
Картинки вне static
Импорт картинок, лежащих не глобально в static
, а где-нибудь в src/assets
, можно произвести
следующим образом:
// src/pages/index.js
import React from "react"
import image from "assets/images/great.jpg"
export default function HomePage() {
return (
<main>
<img src={image} alt="Great Gatsby" width={375} height="auto" />
</main>
)
}
Dark mode
В мобильных приложениях появился dark mode. Осмелюсь доложить, штука неплохая. Вечерами спасает глаза. Те, кто хочет завести себе PWA и быть ближе к нативным мобильным, явно захотят и эту фичу. Что ж, сделаем!
Я опишу сложный вариант, чтобы продемонстрировать работу с gatsby-browser.js
.
Можно проще: менять класс у html
или body
(рабоать это будет, конечно, до перезагрузки страницы).
Понадобятся стили, общие для всех страниц для объявления в них css-переменных. В примере динамически меняться будут только фон и текст страницы.
/* assets/styles/base.css */
:root {
--textColor: #3f3f3f;
--bgColor: #fafafa;
}
[data-theme="dark"] {
--textColor: #d9d7e0;
--bgColor: #232129;
}
body {
background-color: var(--bgColor);
color: var(--textColor);
}
Идём на сторону клиента. Код в onClientEntry
отрабатывает только один раз когда посетитель зашёл
на страницу.
// gatsby-browser.js
// подключаем глобальные стили
require("./src/assets/styles/base.css")
exports.onClientEntry = () => {
enableTheme()
}
// Если тема не установлена, применить тему по-умолчанию.
// Если установлена пользователем, сохранить его выбор в localStorage,
// чтобы не заставлять человека выбирать её снова и снова при переходе на другие страницы
function enableTheme() {
const root = document.getElementsByTagName("body")[0]
try {
const uiTheme = localStorage.getItem("theme-ui-color-mode")
const theme = uiTheme ? uiTheme : "light"
// выставить data-атрибут темы для элемента body
root.setAttribute("data-theme", theme)
} catch (error) {
console.error('localStorage error', error);
}
}
Неплохо бы сделать переключатель, чтобы пользователь сам мог менять оформление.
// src/components/theme-switcher.js
import React from "react"
const onThemeToggle = () => {
// получить тему из data-theme
const root = document.getElementsByTagName("body")[0]
const theme = root.getAttribute("data-theme")
// изменить атрибут
// если была светлая тема, поставить тёмную и наоборот
const uiTheme = theme === "dark" ? "light" : "dark"
try {
root.setAttribute("data-theme", uiTheme)
// запомнить выбор
localStorage.setItem("theme-ui-color-mode", uiTheme)
} catch (error) {
return false
}
}
export default function ThemeSwitcher() {
return (
<button onClick={onThemeToggle} type="button">
Change Theme
</button>
)
}
Ну, и подключить компонент-switcher куда-нибудь на страницу или в другой компонент: в шапку сайта, например.
// src/pages/index.js
import React from "react"
import Layout from "components/layout"
import ThemeSwitcher from "components/theme-switcher"
export default function HomePage({ pageContext: { language } }) {
return (
<Layout lang={language}>
<ThemeSwitcher />
<h1>Home</h1>
</Layout>
)
}
Проверяем, кликаем.
Если хочется менять тему в зависимости от времени суток, лучший вариант prefers-color-scheme. Тогда приведённый выше код даже писать не придётся.
Подсветка кода
Когда вы пишете в своих заметках тонны кода как это делаю я, подсветка жизненно необходима. Для gatsby есть два варианта: prism.js или highlight.js. Возьмём первый.
$ npm i prismjs gatsby-remark-prismjs
Настроек у пакета достаточно. Не вижу смысла описывать их все здесь, лучше увидеть полную картину.
Ограничимся минимумом. Надо сказать gatsby-plugin-mdx
, чтобы он применил prism.js
.
// gatsby-config.js
module.exports = {
plugins: [
// ...
{
resolve: `gatsby-plugin-mdx`,
options: {
gatsbyRemarkPlugins: [
{
resolve: `gatsby-remark-prismjs`,
options: {
classPrefix: "language-",
inlineCodeMarker: null,
},
},
],
},
},
],
}
Не забыть подключить стили подсветки. Можно глобально, а можно только на страницах записей.
// gatsby-browser.js
require("prismjs/themes/prism-solarizedlight.css")
И посмотреть-таки, что получилось.
<!--content/ru/frontend/gatsby/gatsby.mdx-->
Кусок кода css.
```css
.gatsby-highlight {
background-color: #fdf6e3;
border-radius: 0.3em;
}
```
Комментарии
Редкий блог обходится без комментариев. Здесь у каждого свои предпочтения, начиная от выбора самой системы комментирования, и заканчивая реализацией её подключения. Мне интересно поведение, когда комментарии показываются только если посетитель сам захотел их увидеть. Следовательно, до этого момента (а он может не наступить никогда), я не хочу грузить какие-то сторонние скрипты.
Реализация именно такого поведения и представлена ниже. Из всего многообразия похожих скриптовых систем используем disqus.
Код disqus.
// src/components/disqus.js
const DISQUS_ID = "xxxxx.disqus.com" // ваш идентификатор
export const runDisqus = () => (function () {
const page = window.document;
const dscript = page.createElement("script");
dscript.src = `//${DISQUS_ID}/embed.js`;
dscript.setAttribute("data-timestamp", +new Date());
(page.head || page.body).appendChild(dscript);
})();
Компонент комментариев.
// src/components/comments.js
import React, { useState, useEffect } from "react"
import { runDisqus } from "./disqus"
const Comments = () => {
// Если юзер тыкнул по кнопке (isThreadOpened = true),
// динамически подключаем скрипт disqus
const [isThreadOpened, setIsThreadOpened] = useState(false)
const handleOpenThread = () => {
setIsThreadOpened(true)
if (typeof window !== "undefined") { runDisqus() }
}
// переход с Link не перезагружает страницу полностью
// поэтому выполняем DISQUS.reset, чтобы корректно обновлять
// комментарии на странице
useEffect(() => {
if (window.DISQUS) {
window.DISQUS.reset()
}
}, [])
// показать кнопку, если комментарии ещё не открывали
return (
<div className="comments">
{!isThreadOpened && (
<button onClick={handleOpenThread}>
Show comments
</button>
)}
<div id="disqus_thread" />
</div>
)
}
export default Comments
Подключаем компонент на страницы записей.
// src/templates/post-template.js
import React from "react"
import { graphql } from "gatsby"
import { MDXRenderer } from "gatsby-plugin-mdx"
import Comments from "components/comments"
export default function PostTemplate({ data }) {
const { h1 } = data.mdx.frontmatter
const { body } = data.mdx
return (
<main>
<h1>{h1}</h1>
<MDXRenderer>{body}</MDXRenderer>
<Comments />
</main>
)
}
// ...
Ещё вариант: подключать скрипт когда скролл доходит до конца страницы, чтобы ленивцы не напрягали палец нажатием на кнопку.
MDX
Plugin MDX — это в первую очередь доступ к множеству пакетов. Для gatsby они подключаются в двух вариациях: как адаптированный gatsby-пакет или как родной пакет remark.
Выглядит это так:
module.exports = {
plugins: [
{
resolve: "gatsby-plugin-mdx",
options: {
extensions: [".mdx", ".md"],
// родные плагины remark
remarkPlugins: [
// ставит внешним ссылкам атрибуты rel="nofollow, noopener, noreferrer"
// открывает их в новой вкладке
require("remark-external-links"),
],
// адаптированные плагины
gatsbyRemarkPlugins: [
"gatsby-remark-images",
"gatsby-remark-prismjs",
],
},
},
],
}
А самое вкусное то, что можно импортировать компоненты прямо в markdown-файлы. Это позволит сделать почти всё, что угодно. Вывод красивых табличек-предупреждений, нормальные вкладки с табами. Возможно, даже галерею изображений.
Самый простой случай — предупреждения. Делаем.
/* components/tip/tip.module.css */
/* стиль с именем с module.css это css-модуль */
.tip {
padding: 2rem;
color: white;
}
.heading { font-weight: bold; }
.warning { background-color: coral; }
.danger { background-color: crimson; }
Сам компонент:
// components/tip/index.js
import React from "react"
import * as styles from "./tip.module.css"
export default function Tip ({ type, heading, children }) {
return (
<div className={[styles.tip, styles[type]].join(' ')}>
<p className={styles.heading}>{heading}</p>
{children}
</div>
)
}
Использование в mdx-файле:
import Tip from "components/tip"
<Tip heading="Danger!" type="danger">
My danger text
</Tip>
Публикация
Вариантов много. Рассмотрим деплой на github pages.
От знакомства с vuepress у меня остался прекрасный маленький скрипт, который я оставлю здесь, потому как он универсален.
#!/usr/bin/env sh
# abort on errors
set -e
# build and navigate into the build output directory
npm run build && cd public
# if you are deploying to a custom domain
echo 'blogname.com' > CNAME
git init
git add -A
git commit -m 'deploy'
# git push -f git@github.com:<имя_юзера>/<имя_репозитория>.git <имя_ветки>
git push -f git@github.com:jack/jack.github.io.git master:gh-pages
Послесловие
В статье осталось много неосвящённых моментов: как поднять PWA (точно надо?), можно ли брать gatsby когда нужно сотворить Headless CMS + статику (можно), обязательно ли использовать graphql (нет).
Ответы есть в официальной документации. Она очень хороша для глубокого погружения.
С чем сравнивать gatsby когда стоишь перед выбором? Зависит от потребностей.
На первой ступени стоят генераторы статики.
Тот же vuepress был создан в первую очередь для написания документации. И с этой задачей он справляется на 100% без ручного вмешательства. Его можно сравнить с octopress или jekyll. Вернее, из него можно сделать octopress (и даже лучше).
Вторая ступень... не знаю как обозначить эти фреймворки. Gatsby и его молодой аналог на Vue — Gridsome. Они предоставляют больше возможностей: обращение к серверу или напрямую к базе данных, или использование классики в виде локально хранящихся markdown-файлов.
Третья ступень: Next.js/Nuxt.js. Это если нужен server side rendering «из коробки», но одной лишь статикой не обойтись. В основном это приложения с множеством страниц, когда интерфейс подстраивается под каждого пользователя индивидуально.
Про мир Angular сказать не могу, но явно у них есть свои решения.
В любом случае смотреть надо на наличие хорошей документации, поддержки сообщества и того, сколько средств вливается в инструмент. У gatsby в этом плане на текущий момент всё очень неплохо.
У меня остались хорошие впечатления. И я, пожалуй, перееду на gatsby когда надумаю в следующий раз делать глобальный редизайн.