静的サイトジェネレーター「Gatsby.js」について知りたかったのでUdemyのコースで学び、その備忘録となります。
以下の内容となっています。
- 「Gatsby JS: Build static sites with React Wordpress & GraphQL」を参考
- PluginはGatsby.jsの「公式サイト」を参考に設定
- Headless CMS(WordPress)で投稿したdataをGraghQLで取得してGatsby(React)でサイト表示
- 投稿、固定ページ、カスタム投稿ページ、カスタムフィールドに対応
- Contact Formについは未実装
- GraphQLのqueryについては別の記事でまとめています。「Gatsby.jsでWordPressのAPIをGraphQLで取得しるときによく使うquery」
- 静的Webホスティングサービスの「Netlify」でサイト公開する手順は別の記事でまとめています。HeadlessCMS(WordPress)とGatsby.jsで制作したサイトをNetlifyで公開する手順
Set Up
環境
- MAMPでlocal環境構築
- localのphpmyadminでDB接続
API
WordPressが提供しているREST APIからdataを取得するのではなくGraphQLでdata取得
REST API例
http://localhost:8888/myawesomeportfolio.io/wp-json/wp/v2/pages
gatsbyをinstall
$ npm install -g gatsby-cli
WordPressのREST APIをGatsbyにデータをpullさせるプラグインを設定
- npm「gatsby-source-wordpress」をinstall
- 
gatsby-config.jsにplugin設定。公式サイトを参考
- 
baseUrlでMAMPで設定したWordpressのURLを指定
WordPressのdataをNode.jsで読み込みRouting処理
- 
CreatePageを設定- component(適用したいcomponentのpathを指定)
- context(componentのpropsで受け取るvalue)
- path(Routing処理)
 
gatsby-node.js
const _ = require(`lodash`)
const Promise = require(`bluebird`)
const path = require(`path`)
const slash = require(`slash`)
exports.createPages = ({ graphql, actions }) => {
  const { createPage, createRedirect } = actions;
  return new Promise((resolve, reject) => {
    // ==== 固定ページ ====
    graphql(
      `
        {
          allWordpressPage {
            edges {
              node {
                id
                slug
                status
                template
                title
                content
                template
              }
            }
          }
        }
      `
    )
      .then(result => {
        if (result.errors) {
          console.log(result.errors)
          reject(result.errors)
        }
        // Create Page pages.
        const pageTemplate = path.resolve("./src/templates/page.js")
        _.each(result.data.allWordpressPage.edges, edge => {
          createPage({
            path: `/${edge.node.slug}/`,
            component: slash(pageTemplate),
            context: edge.node,
          })
        })
      })
      // ==== END 固定ページ ====
      // ==== 投稿ページ ====
      .then(() => {
        graphql(
          `
            {
              allWordpressPost {
                edges{
                  node{
                    id
                    title
                    slug
                    excerpt
                    content
                  }
                }
              }
            }
          `
        ).then(result => {
          if (result.errors) {
            console.log(result.errors)
            reject(result.errors)
          }
          const postTemplate = path.resolve("./src/templates/post.js")
          _.each(result.data.allWordpressPost.edges, edge => {
            createPage({
              path: `/post/${edge.node.slug}/`,
              component: slash(postTemplate),
              context: edge.node,
            })
          })
          resolve()
        })
      })
    // ==== END 投稿ページ ====
  })
}
- 
context: edge.nodeと指定するとpropsにdataが入る
src/templates/page.js
import React from 'react';
export default ({ pageContext }) => (
  <div>
    <h1>
      {pageContext.title}
    </h1>
  </div>
)
- pageでredirect処理
- root pathで/homeにredirect
const { createRedirect } = actions;
createRedirect({ fromPath: '/', toPath: '/home', redirectInBrowser: true, isPermanent: true })
制作
ツール
- ダミーテキスト生成用のfakerを使用
- メニューのAPIを生成のWordPressプラグイン「WP REST API Menus」をでinstall
GraphQLで取得したdataにLinkを設定
- GatsbyのLink componentを使用
import { graphql, StaticQuery, Link } from 'gatsby';
<StaticQuery query={graphql`
  {
    allWordpressWpApiMenusMenusItems{
      edges{
        node{
          items{
            title
            object_slug
          }
        }
      }
    }
  }
  `} render={props => (
  <div>
    {props.allWordpressWpApiMenusMenusItems.edges[0].node.items.map(item => (
      <Link to={`/${item.object_slug}`} key={item.title}>
        {item.title}
      </Link>
    ))}
  </div>
)} />
styled-componentsを使用
$ yarn add gatsby-plugin-styled-components styled-components babel-plugin-styled-components
- 
gatsby-config.jsのpluginに設定
gatsby-config.js
plugins: [
    `gatsby-plugin-styled-components`,
],
- globalにstyleを設定する場合はcreateGlobalStyleを使用
import styled, { createGlobalStyle } from 'styled-components';
const GlobalStyles = createGlobalStyle`
  @import url('https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i&display=swap');
  body, html {
    font-family: 'Open Sans', san-serif;
    margin: 0 !important;
    padding: 0 !important;
  }
`;
const Layout = ({ children }) => (
 <div>
   <GlobalStyles />
  {children}
 </div>
);
- Linkにstyled-componentsを使用する場合はstyled(Link)を指定
import { graphql, StaticQuery, Link } from 'gatsby';
import styled from 'styled-components';
const ManuItem = styled(Link)`
  color: white;
  display: block;
  padding: 8px 16px;
`;
const MainMenu = () => (
  <StaticQuery query={graphql`
    {
      allWordpressWpApiMenusMenusItems(filter: {
        name: {
          eq: "Main menu"
        }
      }){
        edges{
          node{
            items{
              title
              object_slug
            }
          }
        }
      }
    }
  `} render={props => (
    <MainMenuWrapper>
      {props.allWordpressWpApiMenusMenusItems.edges[0].node.items.map(item => (
        <ManuItem to={`/${item.object_slug}`} key={item.title}>
          {item.title}
        </ManuItem>
      ))}
    </MainMenuWrapper>
  )} />
);
カスタム投稿タイプ「portfolio」を追加
- 
gatsby-config.jsのgatsby-source-wordpressのoptionsに追加
- 
gatby-node.jsに「portfolio」のtemplateを追加
gatsby-config.js
includedRoutes: [
  "**/categories",
  "**/posts",
  "**/pages",
  "**/media",
  "**/tags",
  "**/taxonomies",
  "**/users",
  "**/menus",
  "**/portfolio",
]
gatby-node.js
// ==== カスタム投稿タイプ(portfolio) ====
.then(() => {
  graphql(
    `
      {
        allWordpressWpPortfolio{
          edges{
            node{
              id
              title
              slug
              excerpt
              content
              featured_media{
                source_url
              }
            }
          }
        }
      }
    `
  ).then(result => {
    if (result.errors) {
      console.log(result.errors)
      reject(result.errors)
    }
    const portfolioTemplate = path.resolve("./src/templates/portfolio.js")
    _.each(result.data.allWordpressWpPortfolio.edges, edge => {
      createPage({
        path: `/portfolio/${edge.node.slug}/`,
        component: slash(portfolioTemplate),
        context: edge.node,
      })
    })
    resolve()
  })
})
// ==== カスタム投稿タイプ(portfolio) ====
templateの選択によってレイアウトを変える場合
- WordPressの固定ページにTemplate選択機能を追加、投稿ページに適用
- 
gatsby-node.jsにtemplateの種類を追加、その分岐処理
wp-content/themes/wp-gatsby-js-theme-starter-master/portfolio_under_content.php
<?php /* Template name: Portfolio items below content */ ?>
gatsby-node.js
const pageTemplate = path.resolve("./src/templates/page.js")
const portfolioUnderContentTemplate = path.resolve("./src/templates/portfolioUnderContent.js")
_.each(result.data.allWordpressPage.edges, edge => {
  createPage({
    path: `/${edge.node.slug}/`,
    component: slash(edge.node.template === 'portfolio_under_content.php' ? portfolioUnderContentTemplate : pageTemplate),
    context: edge.node,
  })
})
「Advanced Custom Fields」を扱う
- WordPressの「Advanced Custom Fields」のAPIを取得プラグインで「ACF to REST API」をinstall
- 
gatsby-node.jsでGraphQLで取得するdataにacfを追加
gatsby-node.js
graphql(
  `
    {
      allWordpressWpPortfolio{
        edges{
          node{
            id
            title
            slug
            excerpt
            content
            featured_media{
              source_url
            }
            acf{
              portfolio_url
            }
          }
        }
      }
    }
  `
)
投稿一覧画面にページネーション実装
- 
gatsby-node.jsでGraphQLで展開したdataを元にCreatePageを設定
gatsby-node.js
const posts = result.data.allWordpressPost.edges;
const postsPerPage = 2;
const numberOfPages = Math.ceil(posts.length / postsPerPage);
const blogPostListTemplate = path.resolve('./src/templates/blogPostList.js');
Array.from({ length: numberOfPages }).forEach((page, index) => {
  createPage({
    component: slash(blogPostListTemplate),
    path: index === 0 ? '/blog' : `/blog/${index + 1}`,
    context: {
      posts: posts.slice(index * postsPerPage, (index * postsPerPage) + postsPerPage),
      numberOfPages,
      currentPage: index + 1
    },
  });
});
- 
CreatePageのdataをtemplatesで画面制作
- 
pageContext.postsはcontextで設定したpostsで、GraphQLから取得したdata
- 
Array.from({ length: pageContext.numberOfPages})でページャの番号を生成
src/templates/blogPostList.js
const blogPostList = ({ pageContext }) => (
  <Layout>
    {
      pageContext.posts.map(post => (
        <div key={post.node.wordpress_id}>
          <h3 dangerouslySetInnerHTML={{ __html: post.node.title }} />
          <p dangerouslySetInnerHTML={{ __html: post.node.content }} />
        </div>
      ))
    }
    {
      Array.from({ length: pageContext.numberOfPages}).map((page, index) => (
        <div key={index}>
          <Link to={index === 0 ? '/blog' : `/blog/${index + 1}`}>
            {index + 1}
          </Link>
        </div>
      ))
    }
  </Layout>
);
export default blogPostList;
一覧画面のページネーションでCurrentPageかを判定
- style-componentsでpropsを受け取って判定
- 
pageContext.currentPageはgatsby-node.jsのcontextで設定した値
import styled from 'styled-components';
const PageNumberWrapper = styled.div`
  border: 1px solid #eee;
  background-color: ${props => props.isCurrentPage ? '#eee' : 'white'};
`;
<PageNumberWrapper key={index} isCurrentPage={index + 1 === pageContext.currentPage}>
GraphQLで日付フォーマットを指定
- 
formatStringで指定
{
  allWordpressPost{
    edges{
      node{
        excerpt
        date(formatString: "YYYY/MM/DD hh:mm")
      }
    }
  }
}
その他
WordPressにカスタム投稿タイプ「portfolio」を追加
- WordPressのthemeで管理しているfunctions.phpに記述
functions.php
<?php
add_theme_support( 'custom-logo' );
add_theme_support( 'menus' );
add_theme_support('post-thumbnails');
function create_custom_portfolio_post_type() {
	register_post_type('portfolio', 
    array(
      'labels' => array(
        'name' => __('portfolio'),
    	  'singular_name' => __('portfolio')
      ),
      'public' => true,
      'show_in_admin_bar' => true,
      'show_in_rest' => true,
    ));
	add_post_type_support('portfolio', array('thumbnail', 'excerpt'));
}
add_action('init', 'create_custom_portfolio_post_type');
