Использование Gatsby для создания блога с разбивкой на страницы для сообщений блога

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

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

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

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

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

Затем вы создаете статические веб-страницы из написанного кода. Плагины для Gatsby предоставят вам желаемую функциональность.

Чтобы создать статический веб-сайт или блог, нужно пройти несколько шагов. Кроме того, вы не можете использовать большинство сторонних компонентов React с Gatsby, поскольку они динамические.

Кроме того, служебная программа командной строки имеет проблемы с путями с учетом регистра в Windows, что приводит к RelayParser: Encountered duplicate definitions for one or more documents из-за дублирования путей к файлам без учета регистра в различных частях папки проекта.

Следовательно, Linux или Mac OS, вероятно, являются лучшим выбором для использования Gatsby CLI. Кроме того, в одном файле должны выполняться запросы нескольких страниц, и все они передаются шаблонам в одном файле.

gatsby-node.js в проекте - это место, где выполняется запрос содержимого.

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

У нас будет шаблон для отображения всех записей блога с разбивкой на страницы по пять сообщений в каждой. Также у нас будет страница категорий для отображения списка статей с заданным тегом. Для стилизации будем использовать Bootstrap.

Начнем с установки Gatsby CLI, запустив npm install -g gatsby-cli. После того, как он установлен, мы запускаем программу CLI, чтобы добавить начальный код для веб-сайта. Мы запускаем gatsby new gatsby-blog, чтобы создать новый сайт.

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

Установите пакеты, запустив:

npm i @mdx-js/mdx @mdx-js/react bootstrap gatsby-image gatsby-paginate gatsby-plugin-manifest gatsby-plugin-mdx gatsby-plugin-offline gatsby-plugin-react-helmet gatsby-plugin-sharp gatsby-remark-images gatsby-source-filesystem gatsby-transformer-remark gatsby-transformer-sharp jquery lodash popper.js react-helmet

Основными пакетами в этом списке являются gatsby-image для отображения изображений, gatsby-source-filesystem для получения файлов разметки и отображения их в наших шаблонах.

react-helmet за добавление тега head в наш блог для включения внешних файлов CSS и скриптов. gatsby-transformer-remark предназначен для преобразования разметки в текст HTML. gatsby-transformer-sharp предназначен для преобразования изображений из различных источников для отображения на нашем веб-сайте.

Завершив установку пакета, мы можем приступить к написанию кода. В папке components сначала работаем над header.js.

Там мы помещаем:

import { Link } from "gatsby"
import PropTypes from "prop-types"
import React from "react"
import { StaticQuery, graphql } from "gatsby"
import _ from "lodash"
import "./header.css"
const Header = ({ siteTitle }) => (
  <nav className="navbar navbar-expand-md bg-dark navbar-dark">
    <a className="navbar-brand" href="#">
      {siteTitle}
    </a>
    <button
      className="navbar-toggler"
      type="button"
      data-toggle="collapse"
      data-target="#collapsibleNavbar"
    >
      <span className="navbar-toggler-icon"></span>
    </button>
    <div className="collapse navbar-collapse" id="collapsibleNavbar">
      <ul className="navbar-nav">
        <li className="nav-item">
          <Link to="blog/page/1" className="nav-link">
            Home
          </Link>
        </li>
        <li className="nav-item dropdown">
          <a
            className="nav-link dropdown-toggle"
            href="#"
            id="navbarDropdown"
            role="button"
            data-toggle="dropdown"
            aria-haspopup="true"
            aria-expanded="false"
          >
            Categories
          </a>
          <div className="dropdown-menu" aria-labelledby="navbarDropdown">
            <StaticQuery
              query={graphql`
                query CategoryQuery {
                  allMarkdownRemark(limit: 2000) {
                    group(field: frontmatter___tags) {
                      fieldValue
                    }
                  }
                }
              `}
              render={data => {
                return data.allMarkdownRemark.group.map(g => {
                  return (
                    <Link
                      to={`tags/${g.fieldValue}`}
                      className="dropdown-item"
                    >
                      {_.capitalize(g.fieldValue)}
                    </Link>
                  )
                })
              }}
            />
          </div>
        </li>
      </ul>
    </div>
  </nav>
)
Header.propTypes = {
  siteTitle: PropTypes.string,
}
Header.defaultProps = {
  siteTitle: ``,
}
export default Header

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

В header.css мы помещаем:

.navbar-brand {
  font-size: 20px;
  margin-top: -20px;
}
nav.navbar {
  padding-bottom: 0px;
  height: 60px;
}

Чтобы изменить высоту панели навигации и изменить размер шрифта и поля.

Далее работаем над общей версткой блога. Мы создаем файл с именем layout.js в папке components, если он еще не существует, и добавляем:

/**
 * Layout component that queries for data
 * with Gatsby's useStaticQuery component
 *
 * See: https://www.gatsbyjs.org/docs/use-static-query/
 */
import React from "react"
import PropTypes from "prop-types"
import { useStaticQuery, graphql } from "gatsby"
import { Helmet } from "react-helmet"
import Header from "./header"
import "./layout.css"
const Layout = ({ children }) => {
  const data = useStaticQuery(graphql`
    query SiteTitleQuery {
      site {
        siteMetadata {
          title
        }
      }
    }
  `)
  return (
    <>
      <Helmet>
        <title>Gatsby Blog</title>
        <script
          src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
          integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
          crossOrigin="anonymous"
        ></script>
        <script
          src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
          integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
          crossOrigin="anonymous"
        ></script>
        <script
          src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
          integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
          crossOrigin="anonymous"
        ></script>
        <link
          href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
          rel="stylesheet"
          integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
          crossOrigin="anonymous"
        />
      </Helmet>
      <Header siteTitle={data.site.siteMetadata.title} />
      <div
        style={{
          margin: `0 auto`,
          maxWidth: 960,
          padding: `0px 1.0875rem 1.45rem`,
          paddingTop: 0,
        }}
      >
        <main>{children}</main>
        <footer>
          © {new Date().getFullYear()}, Built with
          {` `}
          <a href="https://www.gatsbyjs.org">Gatsby</a>
        </footer>
      </div>
    </>
  )
}
Layout.propTypes = {
  children: PropTypes.node.isRequired,
}
export default Layout

В этом файле мы добавляем тег head для наших HTML-страниц. Мы включаем Bootstrap CSS и файлы кода и jQuery в компонент Helmet, который будет преобразован в тег head на наших страницах.

Далее мы добавляем наш контент, создаем папку markdown-pages в папке src. Затем мы создаем несколько файлов уценки.

Добавляем в папку markdown-pages следующие файлы:

check-variable-number.md
clone-array.md
concat-array.md
exponentiation.md
get-length-obj.md
repeat-string.md
send-email.md

Затем в check-variable-number.md мы добавляем:

---
path: "/blog/check-variable-number"
date: "2019-05-04"
title: "How to Check if a Variable is a Number"
tags: ["number"]
---
We can check if a variable is a number in multiple ways.
## isNaN
We can check by calling `isNaN` with the variable as the argument. It also detects if a string’s content is a number. For example:
```
isNaN(1) // false
isNaN('1') // false
isNaN('abc') // true
```
**Note:** `isNaN(null)` is `true` .
### typeof Operator
We can use the `typeof` operator before a variable to check if it’s a number, like so:
```
typeof 1 == 'number' // true
typeof '1' == 'number' // false
```
![](https://cdn-images-1.medium.com/max/800/1*3X6EiKc-njoRpCB1AWnv3Q.png)

В clone-array.md мы добавляем:

---
path: "/blog/clone-array"
date: "2019-05-04"
title: "How to Clone Array in JavaScript"
tags: ["array"]
---
There are a few ways to clone an array in JavaScript,
### Object.assign
`Object.assign` allows us to make a shallow copy of any kind of object including arrays.
For example:
<pre>const a = [1,2,3];
const b = Object.assign([], a); // [1,2,3]</pre>
### Array.slice
The `Array.slice` function returns a copy of the original array.
For example:
<pre>const a = [1,2,3];
const b = a.slice(0); // [1,2,3]</pre>
### Array.from
The `Array.slice` function returns a copy of the original array. It takes array like objects like `Set` and it also takes an array as an argument.
<pre>const a = [1,2,3];
const b = Array.from(a); // [1,2,3]</pre>
### Spread Operator
The fastest way to copy an array, which is available with ES6 or later, is the spread operator.
<pre>const a = [1,2,3];
const b = [...a]; // [1,2,3]</pre>
### JSON.parse and JSON.stringify
This allows for deep copy of an array and only works if the objects in the array are plain objects. It can be used like this:
<pre>const a = [1,2,3];
const b = JSON.parse(JSON.stringify(a)); // [1,2,3]</pre>

Далее в concat-array.md мы добавляем:

---
path: "/blog/concat-array"
date: "2019-05-04"
title: "How to Concatenate Array in JavaScript"
tags: ["array"]
---
There are a few ways to concatenate arrays in JavaScript.
## Array.concat
We can all `Array.concat` on an array to combine 2 arrays and return the new one. For example:
```
const a = [1,2,3];
const b = [4,5];
const c = a.concat(b) // [1,2,3,4,5]
```
## Array.push
We can push elements of one array into another.
```
const a = [1,2,3];
const b = [4,5];
let c = Object.assign([], a);
for (let i = 0; i < b.length; i++){
  c.push(b[i]);
}
console.log(c); // [1,2,3,4,5]
```
What we did is make a copy of `a` and assigned it to `c` , then pushed the elements of `b` by looping through it and adding them to the end of `c` .
## Spread Operator
With ES6 or later, we can use the spread operator to spread the items from another array into a new array by doing the following:
```
const a = [1,2,3];
const b = [4,5];
const c = [...a, ...b];
console.log(c); // [1,2,3,4,5]
```
![](https://cdn-images-1.medium.com/max/800/1*3X6EiKc-njoRpCB1AWnv3Q.png)

В exponentiation.md мы добавляем:

---
path: "/blog/exponentiation"
date: "2019-05-04"
title: "How to do Exponentiation in JavaScript"
tags: ["number"]
---
There are multiple to compute exponents with JavaScript.
The newest way is the exponentiation operator `**`, available with ES2016 or higher.
For example, we can do this:
```
const a = 2 ** 3; // 8
```
It is right associative, so `a ** b ** c` is equal to `a ** (b ** c)`. This works with all exponents.
For example:
```
const a = 2 ** (3 ** 4);
const b = 2 ** 3 ** 4;
a == b // true, both are 2.4178516392292583e+24
```
Detail browser compatibility is available at [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Browser_compatibility](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Browser_compatibility)
We can also use the `Math.pow` function, like this:
```
const a = Math.pow(2,3) // 8
```
It takes 2 arguments, the first is the base and the second is the exponent. `Math.pow` works with all exponents.
`Math.pow` is compatible with all recent browsers.
![](https://cdn-images-1.medium.com/max/800/1*3X6EiKc-njoRpCB1AWnv3Q.png)

В get-length-obj.md мы добавляем:

---
path: "/blog/get-length-obj"
date: "2019-05-04"
title: "How to Get the Length of An Object"
tags: ["object"]
---
There are 2 ways to get the length of the list of keys of an object.
## Object.keys
`Object.keys` gets the top level list of keys of an object and returns an array of them. For example:
```
const a = {foo: 1, bar: 2};
const length = Object.keys(a).length // 2
```
## Object.getPropertyNames
`Object.getPropertyNames` also gets a list of all top level of keys of an object and return them as an array. For example:
```
const a = {foo: 1, bar: 2};
const length = Object.`getOwnPropertyNames`(a).length // 2
```
## for…in Loop
There is a special loop for looping through the keys of an object. You can do the following:
```
const a = {foo: 1, bar: 2};
let keysCount = 0;
for (let key in a) {
    keysCount++;
}
console.log(`keysCount) // 2
```
![](https://cdn-images-1.medium.com/max/800/1*3X6EiKc-njoRpCB1AWnv3Q.png)

В repeat-strings.md мы добавляем:

---
path: "/blog/repeat-strings"
date: "2019-05-04"
title: "How to Repeat Strings with JavaScript"
tags: ["string"]
---
There are a few ways to repeat a string in JavaScript. JavaScript strings have a built in `repeat()` function. You can also use a loop to do the same thing.
## String.repeat Function
To use the `repeat` function, you pass in the number of times you want to repeat the string as an argument. It returns a new string
For example:
```
const hello = "hello";
const hello5 = A.repeat(5);
console.log(hello5); // "hellohellohellohellohello"
```
## Use a loop
You can use `for` loop and `while` loop to do repeatedly concatenate strings.
Using a `for` loop, you can do:
```
const hello = "hello";
```
With a `while` loop, you can do:
```
const hello = "hello";
```
They both involve increment indexes up to the maximum.
![](https://cdn-images-1.medium.com/max/800/1*3X6EiKc-njoRpCB1AWnv3Q.png)

В send-email.md мы добавляем:

---
path: "/blog/send-email"
date: "2019-05-04"
title: "How to Send Email with SendGrid in Node.js Apps"
tags: ["array"]
---
SendGrid is a great service made by Twilio for sending emails. Rather than setting up your own email server for sending email with your apps, we use SendGrid to do the hard work for us. It also decrease the chance of email ending up in spam since it is a known trustworthy service.
It also has very easy to use libraries for various platforms for sending emails. Node.js is one of the platforms that are supported.
To send emails with SendGrid, install the SendGrid SDK package by running `npm i @sendgrid/mail` . Then in your code, add `const sgMail = require(‘@sendgrid/mail’);` to import the installed package.
Then in your code, you send email by:
```
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
  to: email,
  from: '[email protected]',
  subject: 'Example Email',
  text: `
    Dear user,  Here is your email.`,
  html: `
    <p>Dear user,</p></pre>
        Here is your email.</p>`,
};
sgMail.send(msg);
```
where `process.env.SENDGRID_API_KEY` is the SendGrid’s API, which should be stored as an environment variable since it is a secret.
Testing is easy since you don’t need to set up a local development email server.
Sending email is this simple and easy with SendGrid API. It is also free if you send small amounts of email, which is a great benefit.
![](https://cdn-images-1.medium.com/max/800/1*EdbfsnL3ABxWj2iVWmoIWA.png)

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

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

В 404.js мы заменяем существующий код на:

import React from "react"
import Layout from "../components/layout"
import SEO from "../components/seo"
import Header from "../components/header";
const NotFoundPage = () => (
  <Layout>
    <SEO title="404: Not found" />
    <h1>NOT FOUND</h1>
    <p>You just hit a route that doesn&#39;t exist... the sadness.</p>
  </Layout>
)
export default NotFoundPage

Все, что есть в компоненте Layout, будет применено к этой странице, включая панель навигации и стили Bootstrap.

В index.js мы заменяем существующий код на:

import React from "react"
import Layout from "../components/layout"
import SEO from "../components/seo"
import { useEffect } from "react"
import { navigate } from "gatsby"
const IndexPage = () => {
  useEffect(() => {
    navigate("/blog/page/1")
  }, [])
  return (
    <Layout>
      <SEO title="Home" />
    </Layout>
  )
}
export default IndexPage

Для перенаправления на наш шаблон блога, который мы создадим.

Далее мы создаем наши шаблоны. Мы создаем папку templates в папке src для хранения наших шаблонов.

Там создайте blog-list-template.js и добавьте:

import React from "react"
import { graphql, Link } from "gatsby"
import Layout from "../components/layout"
export default class BlogList extends React.Component {
  constructor(props) {
    super(props)
    this.state = { pageNumArray: [1] }
  }
  componentDidMount() {
    this.setPageArray()
  }
  componentWillReceiveProps() {
    this.setPageArray()
  }
  setPageArray() {
    const totalCount = this.props.data.allMarkdownRemark.totalCount
    const postsPerPage = this.props.data.allMarkdownRemark.pageInfo.perPage
    let pageNumArray = Array.from(
      { length: Math.ceil(totalCount / postsPerPage) },
      (v, i) => i + 1
    )
    this.setState({ pageNumArray })
  }
render() {
    const posts = this.props.data.allMarkdownRemark.edges
    const pathNames = this.props.location.pathname.split("/")
    const page = pathNames[pathNames.length - 1]
    return (
      <Layout>
        {posts.map(({ node }) => {
          const title = node.frontmatter.title || node.fields.slug
          return (
            <div key={node.fields.slug}>
              <h1>{title}</h1>
              <b>Date Posted: {node.frontmatter.date}</b>
              <div dangerouslySetInnerHTML={{ __html: node.html }} />
            </div>
          )
        })}
        <nav aria-label="Page navigation example">
          <ul className="pagination">
            {this.state.pageNumArray.map(p => (
              <li className={`page-item ${page == p ? "active" : ""}`} key={p}>
                <Link className={`page-link`} to={`blog/page/${p}`}>
                  {p}
                </Link>
              </li>
            ))}
          </ul>
        </nav>
      </Layout>
    )
  }
}
export const blogListQuery = graphql`
  query blogListQuery($skip: Int!, $limit: Int!) {
    allMarkdownRemark(
      sort: { fields: [frontmatter___date], order: DESC }
      limit: $limit
      skip: $skip
    ) {
      edges {
        node {
          fields {
            slug
          }
          frontmatter {
            title
            date
          }
          html
        }
      }
      pageInfo {
        perPage
      }
      totalCount
    }
  }
`

Здесь отобразите контент, который предоставляет запрос, который мы добавим в gatsby-node.js.

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

Функция setPageArray получает номера страниц, получая общее количество страниц и затем устанавливая его в состояние компонента, чтобы у нас были ссылки для перехода на страницу, которая отображается с помощью:

{this.state.pageNumArray.map(p => (
   <li className={`page-item ${page == p ? "active" : ""}`} key={p}>
     <Link className={`page-link`} to={`blog/page/${p}`}>
     {p}
     </Link>
   </li>
))}

Сообщения в блоге обрабатываются:

{posts.map(({ node }) => {
  const title = node.frontmatter.title || node.fields.slug
  return (
    <div key={node.fields.slug}>
      <h1>{title}</h1>
      <b>Date Posted: {node.frontmatter.date}</b>
      <div dangerouslySetInnerHTML={{ __html: node.html }} />
      </div>
  )
})}

Гэтсби очищает HTML, чтобы мы могли отображать его, задав его напрямую с помощью dangerouslySetInnerHTML. Далее мы делаем шаблон для отображения постов с определенными тегами.

Для этого создаем tags.js в папке templates и добавляем:

import React from "react"
import PropTypes from "prop-types"
import Layout from "../components/layout"
import _ from "lodash"
// Components
import { Link, graphql } from "gatsby"
const Tags = ({ pageContext, data }) => {
  const { tag } = pageContext
  const { edges, totalCount } = data.allMarkdownRemark
  const tagHeader = `${totalCount} post${
    totalCount === 1 ? "" : "s"
  } tagged with "${_.capitalize(tag)}"`
  return (
    <Layout>
      <h1>{tagHeader}</h1>
      {edges.map(({ node }) => {
        const { slug } = node.fields
        const { title, date } = node.frontmatter
        return (
          <div key={slug}>
            <h1>{title}</h1>
            <b>Date Posted: {date}</b>
            <div dangerouslySetInnerHTML={{ __html: node.html }} />
          </div>
        )
      })}
    </Layout>
  )
}
Tags.propTypes = {
  pageContext: PropTypes.shape({
    tag: PropTypes.string.isRequired,
  }),
  data: PropTypes.shape({
    allMarkdownRemark: PropTypes.shape({
      totalCount: PropTypes.number.isRequired,
      edges: PropTypes.arrayOf(
        PropTypes.shape({
          node: PropTypes.shape({
            frontmatter: PropTypes.shape({
              title: PropTypes.string.isRequired,
            }),
            fields: PropTypes.shape({
              slug: PropTypes.string.isRequired,
            }),
          }),
        }).isRequired
      ),
    }),
  }),
}
export default Tags
export const pageQuery = graphql`
  query($tag: String) {
    allMarkdownRemark(
      limit: 2000
      sort: { fields: [frontmatter___date], order: DESC }
      filter: { frontmatter: { tags: { in: [$tag] } } }
    ) {
      totalCount
      edges {
        node {
          fields {
            slug
          }
          frontmatter {
            title
            date
          }
          html
        }
      }
    }
  }
`

Работает аналогично blog-list-template.js. Запрос GraphQL внизу получает сообщения, помеченные данным тегом, а затем отображает их, запустив приведенный выше код компонента.

Метаданные наших страниц находятся в объекте frontmatter каждой записи.

Затем в gatsby-config.js мы заменяем существующий код на:

module.exports = {
  siteMetadata: {
    title: `Gatsby Blog`,
    description: `Gatsby Blog`,
    author: `@gatsbyjs`,
  },
  plugins: [
    `gatsby-plugin-react-helmet`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-plugin-mdx`,
      options: {
        gatsbyRemarkPlugins: [
          {
            resolve: `gatsby-remark-images`,
            options: {
              maxWidth: 1200,
            },
          },
        ],
      },
    },
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `gatsby-starter-default`,
        short_name: `starter`,
        start_url: `/blog/page/1`,
      },
    },
    // this (optional) plugin enables Progressive Web App + Offline functionality
    // To learn more, visit: https://gatsby.dev/offline
    // `gatsby-plugin-offline`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `markdown-pages`,
        path: `${__dirname}/src/markdown-pages`,
      },
    },
    `gatsby-plugin-react-helmet`,
    {
      resolve: `gatsby-transformer-remark`,
      options: {
        plugins: [
          {
            resolve: `gatsby-remark-images`,
            options: {
              // It's important to specify the maxWidth (in pixels) of
              // the content container as this plugin uses this as the
              // base for generating different widths of each image.
              maxWidth: 590,
            },
          },
        ],
      },
    },
  ],
}

Чтобы все установленные нами пакеты запустились.

Наконец, в gatsby-node.js мы заменяем существующий код на:

const _ = require("lodash")
const path = require("path")
const { createFilePath } = require("gatsby-source-filesystem")
exports.createPages = async ({ graphql, actions, reporter }) => {
  const { createPage } = actions
  const result = await graphql(
    `
      {
        postRemark: allMarkdownRemark(
          sort: { fields: [frontmatter___date], order: DESC }
          limit: 1000
        ) {
          edges {
            node {
              fields {
                slug
              }
            }
          }
        }
        tagsGroup: allMarkdownRemark(limit: 2000) {
          group(field: frontmatter___tags) {
            fieldValue
          }
        }
      }
    `
  )
  if (result.errors) {
    reporter.panicOnBuild(`Error while running GraphQL query.`)
    return
  }
  // ...
  // Create blog-list pages
  const posts = result.data.postRemark.edges
  const postsPerPage = 5
  const numPages = Math.ceil(posts.length / postsPerPage)
  Array.from({ length: numPages }).forEach((_, i) => {
    createPage({
      path: `/blog/page/${i + 1}`,
      component: path.resolve("./src/templates/blog-list-template.js"),
      context: {
        limit: postsPerPage,
        skip: i * postsPerPage,
        numPages,
        currentPage: i + 1,
      },
    })
  })
const tags = result.data.tagsGroup.group
  tags.forEach(tag => {
    createPage({
      path: `/tags/${_.kebabCase(tag.fieldValue)}/`,
      component: path.resolve("./src/templates/tags.js"),
      context: {
        tag: tag.fieldValue,
      },
    })
  })
}
exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions
  if (node.internal.type === `MarkdownRemark`) {
    const value = createFilePath({ node, getNode })
    createNodeField({
      name: `slug`,
      node,
      value,
    })
  }
}

В приведенном выше файле у нас есть один запрос для получения всех сообщений и последующего создания страниц для их отображения в группах с помощью postRemark запроса ниже:

postRemark: allMarkdownRemark(
  sort: { fields: [frontmatter___date], order: DESC }
    limit: 1000
  ) {
  edges {
    node {
      fields {
        slug
      }
  }
}

Мы создаем файлы страницы с помощью:

const posts = result.data.postRemark.edges
  const postsPerPage = 5
  const numPages = Math.ceil(posts.length / postsPerPage)
  Array.from({ length: numPages }).forEach((_, i) => {
    createPage({
      path: `/blog/page/${i + 1}`,
      component: path.resolve("./src/templates/blog-list-template.js"),
      context: {
        limit: postsPerPage,
        skip: i * postsPerPage,
        numPages,
        currentPage: i + 1,
      },
    })
  })

Он просматривает возвращенные результаты, и они направляют полученную запись в шаблон для создания страниц во время сборки.

tagsGroup запрос ниже:

tagsGroup: allMarkdownRemark(limit: 2000) {
    group(field: frontmatter___tags) {
      fieldValue
    }
}

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

Теперь мы готовы создать статический веб-сайт, запустив gatsby build. Файлы должны быть собраны в папке public.

После этого мы устанавливаем HTTP-сервер для обслуживания встроенных статических файлов. Мы установим пакеты http-server Node.js. Запускаем npm i -g http-server. Затем запустите http-server /path/to/gatsby-blog/public.

После всего этого получаем: