「はじめてつくるGatsbyサイト(Gatsby v4)」コード

第2章 開発編 その1

(#2-s1)

gatsby-portfolio
├── node_modules               
├── src    
├── .gitignore            
├── .prettierrc             
├── gatsby-browser.js       ←削除
├── gatsby-config.js           
├── gatsby-node.js  
├── gatsby-ssr.js           ←削除
├── LICENSE 
├── package-lock.json
├── package.json     
└── README.md            
(#2-s2)

gatsby-portfolio
├── node_modules               
├── src    
├── .gitignore            
├── .prettierrc             
├── gatsby-config.js           
├── gatsby-node.js  
├── LICENSE 
├── package-lock.json
├── package.json     
└── README.md            
(#2-s3)

src
├── components   
│           ├── header.js             ←削除
│           ├── layout.css            ←削除
│           ├── layout.js             ←削除
│           └── seo.js                ←削除
│
├── images  
│           ├── gatsby-astronaut.png  ←削除
│           └── gatsby-icon.png    
│
├── pages 
│           ├── 404.js                ←削除
│           ├── index.js 
│           ├── page-2.js             ←削除
│           ├── using-ssr.js          ←削除
│           └── using-typescript.tsx  ←削除  
│
└── templates 
            └── using-dsg.js          ←削除  
(#2-s4)

src
├── components   
│
├── images
│           └── gatsby-icon.png     
│
├── pages  
│           └──index.js
│
└── templates          
// gatsby-node.js(#2-j1)

↓ 削除
exports.createPages = async ({ actions }) => {
  const { createPage } = actions
  createPage({
    path: "/using-dsg",
    component: require.resolve("./src/templates/using-dsg.js"),
    context: {},
    defer: true,
  })
}
↑ 削除
// index.js(#2-j2)

import * as React from "react"

const Index = () => {
    return (

    )
}

export default Index
// index.js(#2-j3)

import * as React from "react"

const Index = () => {
    return (
        <h1>こんにちは</h1>   // 追加
    )
}

export default Index

// style.css(#2-c1)

.h1-style {
    color: red;
    letter-spacing: 20px;
}
(#2-s5)

src
├── components   
│
├── images  
│           └── gatsby-icon.png   
│
├── pages 
│           └── index.js
│
├── styles      ←作成
│
└── templates
          
(#2-s6)

src
├── components   
│
├── images  
│           └── gatsby-icon.png   
│
├── pages 
│           └── index.js
│
├── styles
│           └── index.module.css      ←作成
│
└── templates
          
// index.module.css(#2-c2)

.h1Text{
    color: red;
    letter-spacing: 20px;
}
//index.js(#2-j4)

import * as React from "react"
import * as style from "../styles/index.module.css"   // 追加

const Index = () => {
    return (
        <h1>こんにちは</h1> 
    )
}

export default Index

//index.js(#2-j5)

import * as React from "react"
import * as style from "../styles/index.module.css"  

const Index = () => {
    return (
        <h1 className={style.h1Text}>こんにちは</h1>  // 追加
    )
}

export default Index
(#2-s7)

src
├── components   
│
├── images  
│           └── gatsby-icon.png   
│
├── pages 
│           ├── blog.js     ←作成
│           └── index.js
│
├── styles
│           └── index.module.css
│
└── templates
          
// blog.js(#2-j6)

import * as React from "react"
 
const Blog = () => {
    return (
        <h1>ブログページ</h1>  
    )
}

export default Blog
(#2-s8)

src
├── components   
│
├── images  
│           └── gatsby-icon.png   
│
├── pages 
│           ├── blog.js     
│           ├── contact.js  ←作成
│           └── index.js
│
├── styles
│           └── index.module.css
│
└── templates     
          
// contact.js(#2-j7)

import * as React from "react"
 
const Contact = () => {
    return (
        <h1>コンタクトページ</h1>  
    )
}

export default Contact

//index.js(#2-j8)

import * as React from "react"
import { Link } from "gatsby"   // 追加
import * as style from "../styles/index.module.css"  

const Index = () => {
    return (
        <h1 className={style.h1Text}>こんにちは</h1> 
    )
}

export default Index
//index.js(#2-j9)

import * as React from "react"
import { Link } from "gatsby"
import * as style from "../styles/index.module.css"  

const Index = () => {
    return (
        <h1 className={style.h1Text}>こんにちは</h1> 
        <Link></Link>       // 追加
    )
}

export default Index
//index.js(#2-j10)

import * as React from "react"
import { Link } from "gatsby"
import * as style from "../styles/index.module.css"  

const Index = () => {
    return (
        <h1 className={style.h1Text}>こんにちは</h1> 
        <Link to="/contact">Contactページへ移動</Link>     // 追加  
    )
}

export default Index
//index.js(#2-j11)

import * as React from "react"
import { Link } from "gatsby"
import * as style from "../styles/index.module.css"  

const Index = () => {
    return (
        <div>   // 追加
            <h1 className={style.h1Text}>こんにちは</h1> 
            <Link to="/contact">Contactページへ移動</Link>       
        </div>// 追加
    )
}

export default Index
(#2-s9)

src
├── components   
│
├── data      ←作成
│
├── images
│           └── gatsby-icon.png     
│
├── pages 
│           ├── blog.js 
│           ├── contact.js
│           └── index.js
│
├── styles
│           └── index.module.css
│
└── templates    
          
(#2-s10)

src
├── components   
│
├── data  
│           └── first-blog.md     ←作成
│
├── images
│           └── gatsby-icon.png     
│
├── pages 
│           ├── blog.js 
│           ├── contact.js
│           └── index.js
│
├── styles
│           └── index.module.css
│
└── templates   
          
(#2-s11)

src
├── components   
│
├── data  
│           ├── first-blog.md    
│           ├── second-blog.md    ←作成
│           ├── third-blog.md     ←作成
│           ├── fourth-blog.md    ←作成
│           ├── fifth-blog.md     ←作成
│           └── sixth-blog.md     ←作成
│
├── images
│           └── gatsby-icon.png     
│
├── pages 
│           ├── blog.js 
│           ├── contact.js
│           └── index.js
│
├── styles
│           └── index.module.css
│
└── templates     
          
// gatsby-config.js(#2-j12)

module.exports = {
  siteMetadata: {

  },
  plugins: [

  ],
}
// gatsby-config.js(#2-j13)

...

  plugins: [
    `gatsby-transformer-remark`,    // 追加
    `gatsby-plugin-react-helmet`,
    `gatsby-plugin-image`,

...
// gatsby-config.js(#2-j14)

...

    `gatsby-plugin-image`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },
    `gatsby-transformer-sharp`,

...
// gatsby-config.js(#2-j15)

{
    resolve: `gatsby-source-filesystem`,
    options: {
        name: `blog`,
        path: `${__dirname}/src/data`,
    },
},
// gatsby-config.js(#2-j16)

module.exports = {
  siteMetadata: {
    title: `Gatsby Default Starter`,
    description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`,
    author: `@gatsbyjs`,
    siteUrl: `https://gatsbystarterdefaultsource.gatsbyjs.io/`,
  },
  plugins: [
    `gatsby-transformer-remark`,
    `gatsby-plugin-react-helmet`,
    `gatsby-plugin-image`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `blog`,
        path: `${__dirname}/src/data`,
      },
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `gatsby-starter-default`,
        short_name: `starter`,
        start_url: `/`,
        background_color: `#663399`,
        // This will impact how browsers show your PWA/website
        // https://css-tricks.com/meta-theme-color-and-trickery/
        // theme_color: `#663399`,
        display: `minimal-ui`,
        icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
      },
    },
    // this (optional) plugin enables Progressive Web App + Offline functionality
    // To learn more, visit: https://gatsby.dev/offline
    // `gatsby-plugin-offline`,
  ],
}
// blog.js(#2-j17)

import * as React from "react"
import { graphql } from "gatsby"    // 追加
 
const Blog = () => {
    return (
        <h1>ブログページ</h1>  
    )
}

export default Blog
// blog.js(#2-j18)

...

export default Blog

export const query = graphql``
// blog.js(#2-j19)

...
export default Blog

export const query = graphql`
    query BlogQuery {
        allMarkdownRemark {
            edges {
                node {
                    frontmatter {
                        date
                        excerpt
                        id
                        image
                        title
                    }
                }
            }
        }
    }
`
// blog.js(#2-j20)

import * as React from "react"
import { graphql } from "gatsby"
 
const Blog = () => {
    return (
        <h1>ブログページ</h1>  
    )
}

export default Blog

export const query = graphql`
    query BlogQuery {
        allMarkdownRemark {
            edges {
                node {
                    frontmatter {
                        date
                        excerpt
                        id
                        image
                        title
                    }
                }
            }
        }
    }
`
// blog.js(#2-j21)

import * as React from "react"
import { graphql } from "gatsby"

const Blog = (props) => {    // 追加
    return (
        <h1>ブログページ</h1>  
    )
}

...
// blog.js(#2-j22)

import * as React from "react"
import { graphql } from "gatsby"

const Blog = (props) => {   
    console.log(props)      // 追加
    return (
        <h1>ブログページ</h1>  
    )
}

...
// blog.js(#2-j23)

import * as React from "react"
import { graphql } from "gatsby"

const Blog = (props) => {
    console.log(props)      
    return (
        <h1>ブログページ</h1>
        {props}       // 追加
    )
}

...
// blog.js(#2-j24)

import * as React from "react"
import { graphql } from "gatsby"

const Blog = (props) => {
    console.log(props)      
    return (
        <h1>ブログページ</h1>
        {props.data.allMarkdownRemark.edges}       // 追加
    )
}

...
// blog.js(#2-j25)

import * as React from "react"
import { graphql } from "gatsby"

const Blog = (props) => {
    console.log(props)      
    return (
        <h1>ブログページ</h1>
        {props.data.allMarkdownRemark.edges.map((singleBlog, index))}     // 追加
    )
}

...
// blog.js(#2-j26)

import * as React from "react"
import { graphql } from "gatsby"

const Blog = (props) => {
    console.log(props)      
    return (
        <h1>ブログページ</h1>
        {props.data.allMarkdownRemark.edges.map((singleBlog, index) =>   // 追加
            {console.log(singleBlog)}   // 追加
        )}   
    )
}

...
// blog.js(#2-j27)

import * as React from "react"
import { graphql } from "gatsby"

const Blog = (props) => {
    console.log(props)      
    return (
        <div>    // 追加
            <h1>ブログページ</h1>
            {props.data.allMarkdownRemark.edges.map((singleBlog, index) => 
                {console.log(singleBlog)}   
            )}  
        </div>   // 追加
    )
}

...
// blog.js(#2-j28)

import * as React from "react"
import { graphql } from "gatsby"

const Blog = (props) => {
    console.log(props)    // 削除  
    return (
        <div>    
            <h1>ブログページ</h1>
            {props.data.allMarkdownRemark.edges.map((singleBlog, index) => 
                {console.log(singleBlog)}    // 削除
            )}  
        </div>  
    )
}

...
// blog.js(#2-j29)

import * as React from "react"
import { graphql } from "gatsby"

const Blog = (props) => {
    return (
        <div>    
            <h1>ブログページ</h1>
            {props.data.allMarkdownRemark.edges.map((singleBlog, index) => 
                <div>               // 追加
                    <h2></h2>       // 追加
                    <p></p>         // 追加
                </div>              // 追加
            )}  
        </div>  
    )
}

...
// blog.js(#2-j30)

import * as React from "react"
import { graphql } from "gatsby"

const Blog = (props) => {
    return (
        <div>    
            <h1>ブログページ</h1>
            {props.data.allMarkdownRemark.edges.map((singleBlog, index) => 
                <div>               
                    <h2>{singleBlog.node.frontmatter.title}</h2>       // 追加
                    <p>{singleBlog.node.frontmatter.date}</p>           // 追加
                </div>              
            )}  
        </div>  
    )
}

...
// blog.js(#2-j31)

import * as React from "react"
import { graphql } from "gatsby"

const Blog = (props) => {
    return (
        <div>    
            <h1>ブログページ</h1>
            {props.data.allMarkdownRemark.edges.map((singleBlog, index) => 
                <div key={index}>               // 追加
                    <h2>{singleBlog.node.frontmatter.title}</h2>       
                    <p>{singleBlog.node.frontmatter.date}</p>         
                </div>              
            )}  
        </div>  
    )
}

...
// blog.js(#2-j32)

import * as React from "react"
import { graphql } from "gatsby"
 
const Blog = (props) => {
    return (
        <div>    
            <h1>ブログページ</h1>
            {props.data.allMarkdownRemark.edges.map((singleBlog, index) => 
                <div key={index}>              
                    <h2>{singleBlog.node.frontmatter.title}</h2>       
                    <p>{singleBlog.node.frontmatter.date}</p>         
                </div>              
            )}  
        </div>  
    )
}

export default Blog

export const query = graphql`
    query BlogQuery {
        allMarkdownRemark {
            edges {
                node {
                    frontmatter {
                        date
                        excerpt
                        id
                        image
                        title
                    }
                }
            }
        }
    }
`
(#2-s12)

src
├── components   
│
├── data  
│           ├── first-blog.md    
│           ├── second-blog.md    
│           ├── third-blog.md    
│           ├── fourth-blog.md    
│           ├── fifth-blog.md     
│           └── sixth-blog.md      
│
├── images
│           └── gatsby-icon.png     
│
├── pages 
│           ├── blog.js 
│           └── index.js
│
├── styles
│           └── index.module.css      
│ 
└── templates
            └── single-blog.js      ←追加        
/// single-blog.js(#2-j33)

import * as React from "react"
 
const SingleBlog = () => {
    return (
        <h1>記事ページ</h1>  
    )
}

export default SingleBlog
// gatsby-node.js(#2-j34)

exports.onCreateNode = ({ node }) => {

}
// gatsby-node.js(#2-j35)

exports.onCreateNode = ({ node }) => {
  if (node.internal.type === `MarkdownRemark`) {   // 追加
    
  }
}
// gatsby-node.js(#2-j36)

const { createFilePath } = require(`gatsby-source-filesystem`)   // 追加

exports.onCreateNode = ({ node, getNode, actions }) => {  // 追加
    const { createNodeField } = actions         // 追加
  
    if (node.internal.type === `MarkdownRemark`) {

    }
}
// gatsby-node.js(#2-j37)

const { createFilePath } = require(`gatsby-source-filesystem`)

exports.onCreateNode = ({ node, getNode, actions }) => {
    const { createNodeField } = actions
  
    if (node.internal.type === `MarkdownRemark`) {
      const slug = createFilePath({ node, getNode })   // 追加
      createNodeField({         // 追加
        node,                   // 追加
        name: `slug`,           // 追加
        value: slug,            // 追加
      })
    }
}
// gatsby-node.js(#2-j38)

const { createFilePath } = require(`gatsby-source-filesystem`)

exports.onCreateNode = ({ node, getNode, actions }) => {
    ...
}

exports.createPages = async () => {  // 追加
}                                    // 追加
// gatsby-node.js(#2-j39)

const { createFilePath } = require(`gatsby-source-filesystem`)

exports.onCreateNode = ({ node, getNode, actions }) => {
    ...
}

exports.createPages = async ({ graphql }) => {  // 追加
    ↓追加
    const result = await graphql(`              
        query {
            allMarkdownRemark {
                edges {
                    node {
                        fields {
                            slug
                        }
                    }
                }
            }
        }
    `)
   ↑追加
}
// gatsby-node.js(#2-j40)

const path = require(`path`)     // 追加
const { createFilePath } = require(`gatsby-source-filesystem`)

exports.onCreateNode = ({ node, getNode, actions }) => {
    ...
}

exports.createPages = async ({ graphql, actions }) => {    // 追加
    const { createPage } = actions      // 追加
    const result = await graphql(`              
        query {
            allMarkdownRemark {
                edges {
                    node {
                        fields {
                            slug
                        }
                    }
                }
            }
        }
    `)
  
  ↓追加
  result.data.allMarkdownRemark.edges.forEach(({ node }) => {
        createPage({
            path: node.fields.slug,
            component: path.resolve(`./src/templates/single-blog.js`),
            context: {
                slug: node.fields.slug,
            },
        })
  })
  ↑追加

}
// gatsby-node.js(#2-j41)

const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)

exports.onCreateNode = ({ node, getNode, actions }) => {
    const { createNodeField } = actions
  
    if (node.internal.type === `MarkdownRemark`) {
      const slug = createFilePath({ node, getNode })   
      createNodeField({         
        node,                   
        name: `slug`,           
        value: slug,            
      })
    }
}

exports.createPages = async ({ graphql, actions }) => {  
    const { createPage } = actions   

    const result = await graphql(`              
        query {
            allMarkdownRemark {
                edges {
                    node {
                        fields {
                            slug
                        }
                    }
                }
            }
        }
    `)
  
    result.data.allMarkdownRemark.edges.forEach(({ node }) => {
            createPage({
                path: node.fields.slug,
                component: path.resolve(`./src/templates/single-blog.js`),
                context: {
                    slug: node.fields.slug,
                },
            })
    })
}
// single-blog.js(#2-j42)

import * as React from "react"
import { graphql } from "gatsby"     // 追加
 
const SingleBlog = () => {
    return (
        <h1>記事ページ</h1>  
    )
}

export default SingleBlog

export const query = graphql``      // 追加
// single-blog.js(#2-j43)

import * as React from "react"
import { graphql } from "gatsby" 
 
const SingleBlog = () => {
    return (
        <h1>記事ページ</h1>  
    )
}

export default SingleBlog

export const query = graphql`
    ↓追加
    query SingleBlogQuery {
        markdownRemark {
            frontmatter {
                date
                excerpt
                id
                image
                title
            }
            html
        }
    }
    ↑追加
`
// single-blog.js(#2-j44)

import * as React from "react"
import { graphql } from "gatsby" 
 
const SingleBlog = (props) => {     // 追加
    return (
        <div>                       // 追加
            <h1>記事ページ</h1> 
            {console.log(props)}    // 追加
        </div>                      // 追加
    )
}

export default SingleBlog

export const query = graphql`
    query SingleBlogQuery {
        markdownRemark {
            frontmatter {
                date
                excerpt
                id
                image
                title
            }
            html
        }
    }
`
// single-blog.js(#2-j45)

...

export default SingleBlog

export const query = graphql`
    query SingleBlogQuery ($slug: String!) {     // 追加
        markdownRemark(fields: { slug: { eq: $slug } }) {   // 追加
            frontmatter {
                date
                excerpt
                id
                image
                title
            }
            html
        }
    }
`
// single-blog.js(#2-j46)

...
 
const SingleBlog = (props) => {   
    return (
        <div>                       
            <h1>{props.data.markdownRemark.frontmatter.title}</h1>  // 追加
            <p>{props.data.markdownRemark.frontmatter.date}</p>    // 追加
        </div>                     
    )
}

export default SingleBlog

...
// single-blog.js(#2-j47)

...
 
const SingleBlog = (props) => {   
    return (
        <div>                       
            <h1>{props.data.markdownRemark.frontmatter.title}</h1>
            <p>{props.data.markdownRemark.frontmatter.date}</p> 
            <div dangerouslySetInnerHTML={{ __html: props.data.markdownRemark.html }} />    // 追加
        </div>                     
    )
}

export default SingleBlog

...
// blog.js(#2-j48)

import * as React from "react"
import { graphql } from "gatsby"
 
const Blog = (props) => {
    return (
        <div>    
            <h1>ブログページ</h1>
            {props.data.allMarkdownRemark.edges.map((singleBlog, index) => 
                <div key={index}>              
                    <h2>{singleBlog.node.frontmatter.title}</h2>       
                    <p>{singleBlog.node.frontmatter.date}</p>         
                </div>              
            )}  
        </div>  
    )
}

export default Blog

export const query = graphql`
    ↓置き換え
    query BlogQuery {
        allMarkdownRemark {
            edges {
                node {
                    fields {
                        slug
                    }
                    frontmatter {
                        date
                        excerpt
                        id
                        image
                        title
                    }
                }
            }
        }
    }
    ↑置き換え
`
// blog.js(#2-j49)

import * as React from "react"
import { graphql, Link } from "gatsby"  // Linkを追加
 
const Blog = (props) => {
    return (
        <div>    
            <h1>ブログページ</h1>
            {props.data.allMarkdownRemark.edges.map((singleBlog, index) => 
                <div key={index}>              
                    <Link to={singleBlog.node.fields.slug}><h2>{singleBlog.node.frontmatter.title}</h2></Link>    // 追加      
                    <p>{singleBlog.node.frontmatter.date}</p>         
                </div>              
            )}  
        </div>  
    )
}

...
// blog.js(#2-j50)

...

export default Blog

export const query = graphql`
query BlogQuery {
    allMarkdownRemark(sort: {fields: frontmatter___id, order: DESC}) { // 追加
      edges {
        node {
          frontmatter {
            date
            excerpt
            id
            image
            title
          }
          fields {
            slug
          }
        }
      }
    }
  }
`
// blog.js(#2-j51)

import * as React from "react"
import { graphql, Link } from "gatsby"
 
const Blog = (props) => {
    return (
        <div>    
            <h1>ブログページ</h1>
            {props.data.allMarkdownRemark.edges.map((singleBlog, index) => 
                <div key={index}>              
                    <Link to={singleBlog.node.fields.slug}><h2>{singleBlog.node.frontmatter.title}</h2></Link>        
                    <p>{singleBlog.node.frontmatter.date}</p>         
                </div>              
            )}  
        </div> 
    )
}

export default Blog

export const query = graphql`
    query BlogQuery {
        allMarkdownRemark(sort: {fields: frontmatter___id, order: DESC}) {
            edges {
                node {
                    fields {
                        slug
                    }
                    frontmatter {
                        date
                        excerpt
                        id
                        image
                        title
                    }
                }
            }
        }
    }
`
// single-blog.js(#2-j52)

import * as React from "react"
import { graphql } from "gatsby"
 
const SingleBlog = (props) => {
    return (
        <div>                       
            <h1>{props.data.markdownRemark.frontmatter.title}</h1> 
            <p>{props.data.markdownRemark.frontmatter.date}</p> 
            <div dangerouslySetInnerHTML={{ __html: props.data.markdownRemark.html }} />  
        </div>     
    )
}

export default SingleBlog

export const query = graphql`
    query SingleBlogQuery($slug: String!) {
        markdownRemark(fields: { slug: { eq: $slug } }) {
            frontmatter {
                date
                title
                excerpt
                id
                image
            }
            html
        }
    }
`
// gatsby-node.js(#2-j53)

const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)

exports.onCreateNode = ({ node, getNode, actions }) => {
    const { createNodeField } = actions
  
    if (node.internal.type === `MarkdownRemark`) {
        const slug = createFilePath({ node, getNode })   
        createNodeField({         
            node,                   
            name: `slug`,           
            value: slug,            
        })
    }
}

exports.createPages = async ({ graphql, actions }) => {  
    const { createPage } = actions   

    const result = await graphql(`              
        query {
            allMarkdownRemark {
                edges {
                    node {
                        fields {
                            slug
                        }
                    }
                }
            }
        }
    `)
  
    result.data.allMarkdownRemark.edges.forEach(({ node }) => {
        createPage({
            path: node.fields.slug,
            component: path.resolve(`./src/templates/single-blog.js`),
            context: {
                slug: node.fields.slug,
            },
        })
    })
}
// gatsby-config.js(#2-j54)

module.exports = {
  siteMetadata: {
    title: `Gatsby Default Starter`,
    description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`,
    author: `@gatsbyjs`,
    siteUrl: `https://gatsbystarterdefaultsource.gatsbyjs.io/`,
  },
  plugins: [
    `gatsby-transformer-remark`,
    `gatsby-plugin-react-helmet`,
    `gatsby-plugin-image`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
          name: `blog`,
          path: `${__dirname}/src/data`,
      },
    },
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `gatsby-starter-default`,
        short_name: `starter`,
        start_url: `/`,
        background_color: `#663399`,
        // This will impact how browsers show your PWA/website
        // https://css-tricks.com/meta-theme-color-and-trickery/
        // theme_color: `#663399`,
        display: `minimal-ui`,
        icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
      },
    },
    // this (optional) plugin enables Progressive Web App + Offline functionality
    // To learn more, visit: https://gatsby.dev/offline
    // `gatsby-plugin-offline`,
  ],
}

第3章 開発編 その2

(#3-s1)

src
├── components   
│
├── data  
│           ├── fifth-blog.md     ←置き換え
│           ├── first-blog.md     ←置き換え
│           ├── fourth-blog.md    ←置き換え
│           ├── second-blog.md    ←置き換え
│           ├── sixth-blog.md     ←置き換え
│           └── third-blog.md     ←置き換え
│
├── images  
│           ├── arrow-left.svg       ←追加
│           ├── arrow-right.svg      ←追加
│           ├── facebook.svg         ←追加
│           ├── gatsby-icon.png      
│           ├── gatsby.svg           ←追加
│           ├── github.svg           ←追加
│           ├── index-hero.jpg       ←追加
│           ├── javascript.svg       ←追加
│           ├── linkedin.svg         ←追加
│           ├── logo.png             ←追加
│           ├── next.svg             ←追加
│           ├── pic1.jpg             ←追加
│           ├── pic2.jpg             ←追加
│           ├── pic3.jpg             ←追加
│           ├── pic4.jpg             ←追加
│           ├── pic5.jpg             ←追加
│           ├── pic6.jpg             ←追加
│           ├── profile.jpg          ←追加
│           ├── react.svg            ←追加
│           ├── social-card.png      ←追加
│           └── twitter.svg          ←追加
│
├── pages 
│           ├── blog.js
│           ├── contact.js
│           └── index.js
│
├── styles
│           ├── all.scss             ←追加 
│           ├── blog.module.scss     ←追加  
│           ├── common.module.scss   ←追加  
│           ├── contact.module.scss  ←追加 
│           ├── index.module.css        ←削除
│           ├── index.module.scss    ←追加    
│           ├── singleBlog.module.scss    ←追加 
│           └── variables.scss       ←追加 
│
└── templates 
            └── single-blog.js

// (#3-j1)

import Img from "gatsby-image"

const OldGatsbyImage = () => {
    return <Img fixed={data.file.childImageSharp.fixed}  />
}

...

export const query = graphql`
  {
    file(relativePath: { eq: "images/example.jpg" }) {
      childImageSharp {
        fixed {
          ...GatsbyImageSharpFixed
        }
      }
    }
  }
`
// blog.js(#3-j2)

...

export const query = graphql`
    query BlogQuery {
        allMarkdownRemark(sort: {fields: frontmatter___id, order: DESC}) {
            edges {
                node {
                    frontmatter {
                        date
                        excerpt
                        id
                        image       // 削除
                        title
                    }
                    fields {
                        slug
                    }
                }
            }
        }
    }
`
/// single-blog.js(#3-j3)

...

export const query = graphql`
    query SingleBlogQuery ($slug: String!) {     
        markdownRemark(fields: { slug: { eq: $slug } }) {   
            frontmatter {
                date
                excerpt
                id
                image       // 削除
                title
            }
            html
        }
    }
`
// index.js(#3-j4)

import * as React from "react"
import { Link } from "gatsby"
import * as style from "../styles/index.module.css"    // 削除

const Index = () => {
    return (
        <div>      
            <h1 className={style.h1Text}>こんにちは</h1>    // 削除
            <Link to="/contact">Contactページへ移動</Link>  // 削除  
        </div>    
    )
}

export default Index
// index.js(#3-j5)

import * as React from "react"
import { Link } from "gatsby"    
import * as style from "../styles/index.module.css"   // 削除
 
const Index = () => {
    return(
    // ↓追加
    <>
        <div>
            <div>
                <h1>I'm Abe Hiroki!</h1>
                <h3>JavaScript Developer</h3>
            </div>
        </div>
        <div>
            <div>
                <div>
                    <h2>JavaScript Nerd</h2>
                    <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
                </div>
            </div>
            <div>
                <h2>Skills</h2>
                <div>
                    <div><span>JavaScript / 10 years</span></div>
                    <div><span>React / 5 years</span></div>
                    <div><span>Gatsby / 3 years</span></div>
                    <div><span>Next.JS / 3 years</span></div>
                </div>
            </div>
            <div>
                <Link to="/contact">Make It Happen!</Link>
            </div>
        </div>
    </>
    // ↑追加
    )
}

export default Index
// index.js(#3-j6)

import * as React from "react"
import { Link } from "gatsby"
import { StaticImage } from "gatsby-plugin-image"  // 追加

const Index = () => {

...
// index.js(#3-j7)

import * as React from "react"
import { Link } from "gatsby"  
import { StaticImage } from "gatsby-plugin-image"   
 
const Index = () => {
    return (
    <>
        <div>
            <StaticImage />    // 追加
            <div>
                <h1>I'm Abe Hiroki!</h1>
                <h3>JavaScript Developer</h3>
            </div>

...
// index.js(#3-j8)

import * as React from "react"
import { Link } from "gatsby"  
import { StaticImage } from "gatsby-plugin-image" 

const Index = () => {
    return (
    <>
        <div>
            <StaticImage src="../images/index-hero.jpg" alt="hero" />    // 追加
            <div>
                <h1>I'm Abe Hiroki!</h1>
                <h3>JavaScript Developer</h3>
            </div>

...
// index.js(#3-j9)

import * as React from "react"
import { Link } from "gatsby"  
import { StaticImage } from "gatsby-plugin-image" 
 
const Index = () => {
    return (
    <>
        <div>
            <StaticImage src="../images/index-hero.jpg" alt="hero" quality={90} placeholder="blurred" formats={["AUTO", "WEBP", "AVIF"]} />    // 追加
            <div>
                <h1>I'm Abe Hiroki!</h1>
                <h3>JavaScript Developer</h3>
            </div>

...
// index.js(#3-j10)

...

    <div>
        <div>
            <h2>JavaScript Nerd</h2>
            <p>Lorem Ipsum is simply dummy text of ....</p>
        </div>
        <StaticImage src="../images/profile.jpg" alt="profile" quality={90} placeholder="blurred" formats={["AUTO", "WEBP", "AVIF"]} />   // 追加
    </div>

...
// index.js(#3-j11)

import * as React from "react"
import { Link } from "gatsby"  
import { StaticImage } from "gatsby-plugin-image"  
import JSLogo from "../images/javascript.svg"   // 追加
import ReactLogo from "../images/react.svg"     // 追加
import GatsbyLogo from "../images/gatsby.svg"   // 追加
import NextLogo from "../images/next.svg"       // 追加
 
const Index = () => {

...
// index.js(#3-j12)

...

    <div>
        <h2>Skills</h2>
        <div>
            <div><img src={JSLogo} alt="javascript"/><span>JavaScript / 10 years</span></div>   // 追加
            <div><img src={ReactLogo} alt="react"/><span>React / 5 years</span></div>   // 追加
            <div><img src={GatsbyLogo} alt="gatsby"/><span>Gatsby / 3 years</span></div>    // 追加
            <div><img src={NextLogo} alt="next"/><span>Next.JS / 3 years</span></div>   // 追加
        </div>
    </div>

...
// index.js(#3-j13)

import * as React from "react"
import { Link } from "gatsby"  
import { StaticImage } from "gatsby-plugin-image" 
import JSLogo from "../images/javascript.svg"  
import ReactLogo from "../images/react.svg"    
import GatsbyLogo from "../images/gatsby.svg"   
import NextLogo from "../images/next.svg"      

const Index = () => {
    return (
        <>    
            <div>
                <StaticImage src="../images/index-hero.jpg" alt="hero" quality={90} placeholder="blurred" formats={["AUTO", "WEBP", "AVIF"]} /> 
                <div>
                    <h1>I'm Abe Hiroki!</h1>
                    <h3>JavaScript Developer</h3>
                </div>
            </div>
            <div>
                <div>
                    <div>
                        <h2>JavaScript Nerd</h2>
                        <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
                    </div>
                    <StaticImage src="../images/profile.jpg" alt="profile" quality={90} placeholder="blurred" formats={["AUTO", "WEBP", "AVIF"]} />  
                </div>
                <div>
                    <h2>Skills</h2>
                    <div>
                        <div><img src={JSLogo} alt="javascript"/><span>JavaScript / 10 years</span></div>   
                        <div><img src={ReactLogo} alt="react"/><span>React / 5 years</span></div>   
                        <div><img src={GatsbyLogo} alt="gatsby"/><span>Gatsby / 3 years</span></div>    
                        <div><img src={NextLogo} alt="next"/><span>Next.JS / 3 years</span></div>   
                    </div>
                </div>
                <div>
                    <Link to="/contact">Make It Happen!</Link>
                </div>
            </div>
        </>  
    )
}

export default Index
// blog.js(#3-j14)

import * as React from "react"
import { graphql, Link } from "gatsby"
 
const Blog = (props) => {
    return (
        <>
          <div>
            <div>
            <h1>Blog</h1>
            <p>エンジニアの日常生活をお届けします</p>
                 {props.data.allMarkdownRemark.edges.map((singleBlog, index) => (
                        <div key={index}>                            
                            <div>
                                <h3>{singleBlog.node.frontmatter.title}</h3>
                                <p>{singleBlog.node.frontmatter.excerpt}</p>
                                <p>{singleBlog.node.frontmatter.date}</p>
                                <Link to={singleBlog.node.fields.slug}>Read More</Link>
                            </div>
                        </div>
                    )
                )}
            </div>
          </div>
        </>     
    )
}

export default Blog

...
// blog.js(#3-j15)

...

export default Blog

export const query = graphql`
    query BlogQuery {
        allMarkdownRemark(sort: {fields: frontmatter___id, order: DESC}) {
            edges {
                node {
                    frontmatter {
                        date
                        excerpt
                        id
                        // ↓追加
                        image {
                            childImageSharp {
                                gatsbyImageData(
                                    quality: 90, 
                                    formats: AUTO, 
                                    placeholder: BLURRED, 
                                )
                            }
                        }
                        // ↑追加
                        title
                    }
                    fields {
                        slug
                    }
                }
            }
        }
    }
`
// (#3-j16)

gatsbyImageData(
    quality: 90
    formats: [AUTO, WEBP, AVIF],    // 変更
    placeholder: BLURRED, 
)
// blog.js(#3-j17)

import * as React from "react"
import { graphql, Link } from "gatsby"
import { GatsbyImage } from "gatsby-plugin-image"   // 追加
 
const Blog = (props) => {
    return (
        <>
          <div>
            <div>
            <h1>Blog</h1>
            <p>エンジニアの日常生活をお届けします</p>
                 {props.data.allMarkdownRemark.edges.map((singleBlog, index) => (
                        <div key={index}>                            
                            <div>
                                <h3>{singleBlog.node.frontmatter.title}</h3>
                                <p>{singleBlog.node.frontmatter.excerpt}</p>
                                <p>{singleBlog.node.frontmatter.date}</p>
                                <Link to={singleBlog.node.fields.slug}>Read More</Link>
                            </div>
                            <GatsbyImage image={singleBlog.node.frontmatter.image.childImageSharp.gatsbyImageData} alt="card-image" />    // 追加
                        </div>
                    )
                )}
            </div>
          </div>
        </> 
    )
}

export default Blog

...
// blog.js(#3-j18)

import * as React from "react"
import { graphql, Link } from "gatsby"
import { GatsbyImage } from "gatsby-plugin-image"
 
const Blog = (props) => {
    return (
        <>
          <div>
            <div>
            <h1>Blog</h1>
            <p>エンジニアの日常生活をお届けします</p>
                 {props.data.allMarkdownRemark.edges.map((singleBlog, index) => (
                        <div key={index}>                            
                            <div>
                                <h3>{singleBlog.node.frontmatter.title}</h3>
                                <p>{singleBlog.node.frontmatter.excerpt}</p>
                                <p>{singleBlog.node.frontmatter.date}</p>
                                <Link to={singleBlog.node.fields.slug}>Read More</Link>
                            </div>
                            <GatsbyImage image={singleBlog.node.frontmatter.image.childImageSharp.gatsbyImageData} alt="card-image" /> 
                        </div>
                    )
                )}
            </div>
          </div>
        </> 
    )
}

export default Blog

export const query = graphql`
    query BlogQuery {
        allMarkdownRemark(sort: {fields: frontmatter___id, order: DESC}) {
            edges {
                node {
                    fields {
                        slug
                    }
                    frontmatter {
                        date
                        excerpt
                        id
                        image {
                            childImageSharp {
                                gatsbyImageData(
                                    quality: 90, 
                                    formats: [AUTO, WEBP, AVIF], 
                                    placeholder: BLURRED, 
                                )
                            }
                        }
                        title
                    }
                }
            }
        }
    }
`
// single-blog.js(#3-j19)

import * as React from "react"
import { graphql } from "gatsby" 
 
const SingleBlog = (props) => {   
    return (
        <>
            <div>

            </div>
            <div>  
                <div>               
                    <h1>{props.data.markdownRemark.frontmatter.title}</h1>
                    <p>{props.data.markdownRemark.frontmatter.date}</p> 
                    <div dangerouslySetInnerHTML={{ __html: props.data.markdownRemark.html }} /> 
                </div> 
            </div>
        </>                    
    )
}

export default SingleBlog

...
// single-blog.js(#3-j20)

...

export default SingleBlog

export const query = graphql`
    query SingleBlogQuery ($slug: String!) {     
        markdownRemark(fields: { slug: { eq: $slug } }) {   
            frontmatter {
                date
                excerpt
                id
                ↓追加
                image {
                    childImageSharp {
                        gatsbyImageData (
                            formats: AUTO, 
                            quality: 90, 
                            placeholder: BLURRED,
                            width: 1000, 
                        )
                    }
                }
                ↑追加
                title
            }
            html
        }
    }
`
// single-blog.js(#3-j21)

import * as React from "react"
import { graphql } from "gatsby" 
import { GatsbyImage } from "gatsby-plugin-image"       // 追加
 
const SingleBlog = (props) => {   
    return (
        <>
            <div>
                <GatsbyImage image={props.data.markdownRemark.frontmatter.image.childImageSharp.gatsbyImageData} alt="blog-image" />    // 追加
            </div>
            <div>  
                <div>               
                    <h1>{props.data.markdownRemark.frontmatter.title}</h1>

...
// single-blog.js(#3-j22)

import * as React from "react"
import { graphql } from "gatsby" 
import { GatsbyImage } from "gatsby-plugin-image" 
 
const SingleBlog = (props) => {   
    return (
        <>
            <div>
                <GatsbyImage image={props.data.markdownRemark.frontmatter.image.childImageSharp.gatsbyImageData} alt="blog-image" />
            </div>
            <div>  
                <div>               
                    <h1>{props.data.markdownRemark.frontmatter.title}</h1>
                    <p>{props.data.markdownRemark.frontmatter.date}</p> 
                    <div dangerouslySetInnerHTML={{ __html: props.data.markdownRemark.html }} /> 
                </div> 
            </div>
        </>                     
    )
}

export default SingleBlog

export const query = graphql`
    query SingleBlogQuery($slug: String!) {     
        markdownRemark(fields: { slug: { eq: $slug } }) {   
            frontmatter {
                date
                excerpt
                id
                image {
                    childImageSharp {
                        gatsbyImageData (
                            formats: [AUTO, WEBP, AVIF],
                            quality: 90, 
                            placeholder: BLURRED,
                            width: 1000, 
                        )
                    }
                }
                title
            }
            html
        }
    }
`
// contact.js(#3-j23)

import * as React from "react"

const Contact = () => {
    return (
        <>
            <div>
                <div>
                    <h1>Contact</h1>
                    <p>お気軽にご連絡ください</p>
                    <form>
                        <label htmlFor="name">お名前</label>  
                        <input type="text" name="name" id="name" required/>
                        <label htmlFor="email">メールアドレス</label>
                        <input type="email" name="email" id="email" required/>
                        <label htmlFor="textarea">ご用件</label>
                        <textarea name="message" rows="10" id="textarea" required></textarea>
                        <button type="submit">送信</button>
                    </form> 
                </div>
            </div>
        </>
    )
}

export default Contact
(#3-s2)

src
├── components   
│           ├── footer.js  ←追加
│           └── header.js    ←追加
│
.
.
.
// header.js(#3-j24)

import * as React from "react"
import { Link } from "gatsby"
import { StaticImage } from "gatsby-plugin-image"

const Header = () => {
  return (
     <header>
       <div>
          <div>
            <Link to="/">
                <StaticImage src="../images/logo.png" alt="logo" quality={90} placeholder="blurred" formats={["AUTO", "WEBP", "AVIF"]} width={50} />
            </Link>
            <ul>
              <li><Link to="/blog">Blog</Link></li>
              <li><Link to="/contact">Contact</Link></li>
            </ul>
          </div>
        </div>
    </header>
  )
}

export default Header
// footer.js(#3-j25)

import * as React from "react"
import { Link } from "gatsby"
import github from "../images/github.svg"
import linkdin from "../images/linkedin.svg"
import twitter from "../images/twitter.svg"
import facebook from "../images/facebook.svg"

const Footer = () => {
    return (
        <footer>
            <div>
                <a href="https://www.google.com/"><img src={github} alt="logo"/></a>
                <a href="https://www.google.com/"><img src={linkdin} alt="logo"/></a>
                <a href="https://www.google.com/"><img src={twitter} alt="logo"/></a>
                <a href="https://www.google.com/"><img src={facebook} alt="logo"/></a>
                <hr/>
                <Link to="/blog">Blog</Link>
                <Link to="/contact">Contact</Link>
                <p>©{new Date().getFullYear()} Abe Hiroki</p>
            </div>
        </footer>
    )
}

export default Footer
(#3-s3)

src
├── components   
│           ├── footer.js        
│           ├── header.js
│           └── layout.js   ←追加
│
.
.
.
// layout.js(#3-j26)

import * as React from "react"
import Header from "./header"
import Footer from "./footer"

const Layout = () => {
  return (
    <>
      <Header />
      <Footer />
    </>
  )
}

export default Layout
// index.js(#3-j27)

import * as React from "react"
import { Link } from "gatsby"  
import { StaticImage } from "gatsby-plugin-image" 
import Layout from "../components/layout"      //追加
import JSLogo from "../images/javascript.svg"  
import ReactLogo from "../images/react.svg"    
import GatsbyLogo from "../images/gatsby.svg"   
import NextLogo from "../images/next.svg"      

const Index = () => {

...
// index.js(#3-j28)

...

const Index = () => {
    return (
        <Layout>   // 置き換え 
            <div>
                <StaticImage src="../images/index-hero.jpg" alt="hero" quality={90} placeholder="blurred" formats={["AUTO", "WEBP", "AVIF"]} /> 
                <div>
                    <h1>I'm Abe Hiroki!</h1>
                    <h3>JavaScript Developer</h3>
                </div>
            </div>
            <div>
                <div>
                    <div>
                        <h2>JavaScript Nerd</h2>
                        <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
                    </div>
                    <StaticImage src="../images/profile.jpg" alt="profile" quality={90} placeholder="blurred" formats={["AUTO", "WEBP", "AVIF"]} />  
                </div>
                <div>
                    <h2>Skills</h2>
                    <div>
                        <div><img src={JSLogo} alt="javascript"/><span>JavaScript / 10 years</span></div>   
                        <div><img src={ReactLogo} alt="react"/><span>React / 5 years</span></div>   
                        <div><img src={GatsbyLogo} alt="gatsby"/><span>Gatsby / 3 years</span></div>    
                        <div><img src={NextLogo} alt="next"/><span>Next.JS / 3 years</span></div>   
                    </div>
                </div>
                <div>
                    <Link to="/contact">Make It Happen!</Link>
                </div>
            </div>
        </Layout>   // 置き換え
    )
}

export default Index
// layout.js(#3-j29)

import * as React from "react"
import Header from "./header"
import Footer from "./footer"

const Layout = (props) => {     // 追加
  return (
    <>
      <Header />
      <main>{props.children}</main>   // 追加
      <Footer />
    </>
  )
}

export default Layout
// blog.js(#3-j30)

import * as React from "react"
import { graphql, Link } from "gatsby"
import { GatsbyImage } from "gatsby-plugin-image" 
import Layout from "../components/layout"   // 追加
 
const Blog = (props) => {
    return(
        <Layout>  // 置き換え
            <div>
            <div>
            <h1>Blog</h1>
            <p>エンジニアの日常生活をお届けします</p>
                {props.data.allMarkdownRemark.edges.map((singleBlog, index) => (
                        <div key={index}>                            
                            <div>
                                <h3>{singleBlog.node.frontmatter.title}</h3>
                                <p>{singleBlog.node.frontmatter.excerpt}</p>
                                <p>{singleBlog.node.frontmatter.date}</p>
                                <Link to={singleBlog.node.fields.slug}>Read More</Link>
                            </div>
                            <GatsbyImage image={singleBlog.node.frontmatter.image.childImageSharp.gatsbyImageData} alt="card-image" />  
                        </div>
                        
                    )
                )}
            </div>
            </div>
        </Layout>  // 置き換え
    )
}

export default Blog

...
// single-blog.js(#3-j31)

import * as React from "react"
import { graphql } from "gatsby" 
import { GatsbyImage } from "gatsby-plugin-image" 
import Layout from "../components/layout"  // 追加
 
const SingleBlog = (props) => {   
    return (
        <Layout>   // 置き換え
            <div>
                <GatsbyImage image={props.data.markdownRemark.frontmatter.image.childImageSharp.gatsbyImageData} alt="blog-image" />
            </div>
            <div>  
                <div>               
                    <h1>{props.data.markdownRemark.frontmatter.title}</h1>
                    <p>{props.data.markdownRemark.frontmatter.date}</p> 
                    <div dangerouslySetInnerHTML={{ __html: props.data.markdownRemark.html }} /> 
                </div> 
            </div>
        </Layout>  // 置き換え             
    )
}

export default SingleBlog

...
// contact.js(#3-j32)

import * as React from "react"
import Layout from "../components/layout"  // 追加

const Contact = () => {
    return (
        <Layout>  // 置き換え
            <div>
                <div>
                    <h1>Contact</h1>
                    <p>お気軽にご連絡ください</p>
                    <form>
                        <label htmlFor="name">お名前</label>  
                        <input type="text" name="name" id="name" required/>
                        <label htmlFor="email">メールアドレス</label>
                        <input type="email" name="email" id="email" required/>
                        <label htmlFor="textarea">ご用件</label>
                        <textarea name="message" rows="10" id="textarea" required></textarea>
                        <button type="submit">送信</button>
                    </form> 
                </div>
            </div>
        </Layout>   // 置き換え
    )
}

export default Contact
// gatsby-config.js(#3-j33)

...

plugins: [
    `gatsby-plugin-sass`,       // 追加
    `gatsby-transformer-remark`,
    `gatsby-plugin-react-helmet`,
    `gatsby-plugin-image`,

...
// index.js(#3-j34)

import * as React from "react"
import { Link } from "gatsby"  
import { StaticImage } from "gatsby-plugin-image" 
import Layout from "../components/layout"
import JSLogo from "../images/javascript.svg"  
import ReactLogo from "../images/react.svg"    
import GatsbyLogo from "../images/gatsby.svg"   
import NextLogo from "../images/next.svg" 
import * as style from "../styles/index.module.scss"    // 追加

...
// blog.js(#3-j35)

import * as React from "react"
import { graphql, Link } from "gatsby"
import { GatsbyImage } from "gatsby-plugin-image"
import Layout from "../components/layout"
import * as style from "../styles/blog.module.scss"    // 追加

...
// single-blog.js(#3-j36)

import * as React from "react"
import { graphql } from "gatsby" 
import { GatsbyImage } from "gatsby-plugin-image"
import Layout from "../components/layout"
import * as style from "../styles/singleBlog.module.scss"    // 追加

...
// contact.js(#3-j37)

import * as React from "react"
import Layout from "../components/layout"
import * as style from "../styles/contact.module.scss"   // 追加

...
// header.js(#3-j38)

import * as React from "react"
import { Link } from "gatsby"
import { StaticImage } from "gatsby-plugin-image"
import * as style from "../styles/common.module.scss"   // 追加

...
// footer.js(#3-j39)

import * as React from "react"
import { Link } from "gatsby"
import github from "../images/github.svg"
import linkdin from "../images/linkedin.svg"
import twitter from "../images/twitter.svg"
import facebook from "../images/facebook.svg"
import * as style from "../styles/common.module.scss"   // 追加

...
// layout.js(#3-j40)

import * as React from "react"
import Header from "./header"
import Footer from "./footer"
import "../styles/all.scss"   // 追加
// index.js(#3-j41)

import * as React from "react"
import { Link } from "gatsby"  
import { StaticImage } from "gatsby-plugin-image" 
import Layout from "../components/layout"
import JSLogo from "../images/javascript.svg"  
import ReactLogo from "../images/react.svg"    
import GatsbyLogo from "../images/gatsby.svg"   
import NextLogo from "../images/next.svg" 
import * as style from "../styles/index.module.scss"  

const Index = () => {
    return (
        <Layout>
            <div className={style.hero}>
                <StaticImage src="../images/index-hero.jpg" alt="hero" quality={90} placeholder="blurred" formats={["AUTO", "WEBP", "AVIF"]} className={style.heroImg} />
                <div className={style.textContainer}>
                    <h1>I'm Abe Hiroki!</h1>
                    <h3>JavaScript Developer</h3>
                </div>
            </div>
            <div className={style.container}>
                <div className={style.profile}>
                    <div>
                        <h2>JavaScript Nerd</h2>
                        <p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
                    </div>
                    <StaticImage src="../images/profile.jpg" alt="profile" quality={90} placeholder="blurred" formats={["AUTO", "WEBP", "AVIF"]} />
                </div>
                <div className={style.skills}>
                    <h2>Skills</h2>
                    <div className={style.skillsContainer}>
                        <div><img src={JSLogo} alt="javascript"/><span>JavaScript / 10 years</span></div>
                        <div><img src={ReactLogo} alt="react"/><span>React / 5 years</span></div>
                        <div><img src={GatsbyLogo} alt="gatsby"/><span>Gatsby / 3 years</span></div>
                        <div><img src={NextLogo} alt="next"/><span>Next.JS / 3 years</span></div>
                    </div>
                </div>
                <div className={style.ctaButton}>
                    <Link to="/contact">Make It Happen!</Link>
                </div>
            </div>
        </Layout>
    )
}

export default Index
// blog.js(#3-j42)

import * as React from "react"
import { graphql, Link } from "gatsby"
import { GatsbyImage } from "gatsby-plugin-image"
import Layout from "../components/layout"
import * as style from "../styles/blog.module.scss"

const Blog = (props) => {
    return (
        <Layout>
            <div className={style.wrapper}>
                <div className={style.container}>
                    <h1>Blog</h1>
                    <p>エンジニアの日常生活をお届けします</p>
                    {props.data.allMarkdownRemark.edges.map((singleBlog, index) => (
                            <div className={style.blogCard} key={index}>                            
                                <div className={style.textContainer}>
                                    <h3>{singleBlog.node.frontmatter.title}</h3>
                                    <p>{singleBlog.node.frontmatter.excerpt}</p>
                                    <p>{singleBlog.node.frontmatter.date}</p>
                                    <Link to={singleBlog.node.fields.slug}>Read More</Link>
                                </div>
                                <GatsbyImage image={singleBlog.node.frontmatter.image.childImageSharp.gatsbyImageData} alt="card-image" className={style.cardImg} />
                            </div>
                        )
                    )}
                </div>
            </div>
        </Layout>
    )
}

export default Blog

export const query = graphql`
query BlogQuery {
    allMarkdownRemark(sort: {fields: frontmatter___id, order: DESC}) {
      edges {
        node {
          frontmatter {
            date
            excerpt
            id
            title
            image {
              childImageSharp {
                  gatsbyImageData(
                      placeholder: BLURRED, 
                      formats: [AUTO, WEBP, AVIF], 
                      quality: 90
                  )
              }
            }
          }
          fields {
            slug
          }
        }
      }
    }
  }
`
// single-blog.js(#3-j43)

import * as React from "react"
import { graphql } from "gatsby" 
import { GatsbyImage } from "gatsby-plugin-image"
import Layout from "../components/layout"
import * as style from "../styles/singleBlog.module.scss"
 
const SingleBlog = (props) => {   
    return(
        <Layout>
            <div className={style.hero}>
                <GatsbyImage image={props.data.markdownRemark.frontmatter.image.childImageSharp.gatsbyImageData} alt="blog-image" />
            </div>
            <div className={style.wrapper}>  
                <div className={style.container}>               
                    <h1>{props.data.markdownRemark.frontmatter.title}</h1>
                    <p>{props.data.markdownRemark.frontmatter.date}</p> 
                    <div dangerouslySetInnerHTML={{ __html: props.data.markdownRemark.html }} /> 
                </div> 
            </div>
        </Layout>                    
    )
}

export default SingleBlog

export const query = graphql`
    query SingleBlogQuery ($slug: String!) {     
        markdownRemark(fields: { slug: { eq: $slug } }) {   
            frontmatter {
                    date
                    excerpt
                    id
                    title
                    image {
                        childImageSharp {
                          gatsbyImageData(            
                                placeholder: BLURRED
                                formats: [AUTO, WEBP, AVIF] 
                                quality: 90
                                width: 1000
                            )
                        }
                    }
            }
            html
        }
    }
`
// contact.js(#3-j44)

import * as React from "react"
import Layout from "../components/layout"
import * as style from "../styles/contact.module.scss"

const Contact = () => {
    return (
        <Layout>
            <div className={style.wrapper}>
                <div className={style.container}>
                    <h1>Contact</h1>
                    <p>お気軽にご連絡ください</p>
                    <form>
                        <label htmlFor="name">お名前</label>  
                        <input type="text" name="name" id="name" required/>
                        <label htmlFor="email">メールアドレス</label>
                        <input type="email" name="email" id="email" required/>
                        <label htmlFor="textarea">ご用件</label>
                        <textarea name="message" rows="10" id="textarea" required></textarea>
                        <button type="submit">送信</button>
                    </form> 
                </div>
            </div>
        </Layout>
    )
}

export default Contact
// header.js(#3-j45)

import * as React from "react"
import { Link } from "gatsby"
import { StaticImage } from "gatsby-plugin-image"
import * as style from "../styles/common.module.scss"

const Header = () => {
  return (
     <header className={style.headerWrapper}>
       <div className={style.container}>
          <div className={style.flexContainer}>
            <Link to="/">
                <StaticImage src="../images/logo.png" alt="logo" quality={90} placeholder="blurred" formats={["AUTO", "WEBP", "AVIF"]} width={50} />
            </Link>
            <ul>
              <li><Link to="/blog">Blog</Link></li>
              <li><Link to="/contact">Contact</Link></li>
            </ul>
          </div>
        </div>
    </header>
  )
}

export default Header
// footer.js(#3-j46)

import * as React from "react"
import { Link } from "gatsby"
import github from "../images/github.svg"
import linkdin from "../images/linkedin.svg"
import twitter from "../images/twitter.svg"
import facebook from "../images/facebook.svg"
import * as style from "../styles/common.module.scss"

const Footer = () => {
    return (
        <footer className={style.footerWrapper}>
            <div className={style.insideContainer}>
                <a href="https://www.google.com/"><img src={github} alt="logo"/></a>
                <a href="https://www.google.com/"><img src={linkdin} alt="logo"/></a>
                <a href="https://www.google.com/"><img src={twitter} alt="logo"/></a>
                <a href="https://www.google.com/"><img src={facebook} alt="logo"/></a>
                <hr/>
                <Link to="/blog">Blog</Link>
                <Link to="/contact">Contact</Link>
                <p>©{new Date().getFullYear()} Abe Hiroki</p>
            </div>
        </footer>
    )
}

export default Footer
// layout.js(#3-j47)

import * as React from "react"
import Header from "./header"
import Footer from "./footer"
import "../styles/all.scss"

const Layout = (props) => {
  return (
    <>
      <Header />
      <main>{props.children}</main>
      <Footer />
    </>
  )
}

export default Layout

第5章 ブラッシュアップ

(#5-s1)

src
.
.
├── pages 
│           ├── 404.js    ←追加
│           ├── blog.js
│           ├── contact.js  
│           └── index.js
.
.
// 404.js(#5-j1)

import * as React from "react"
import Layout from "../components/layout"

const NotFoundPage = () => {
    return(
        <Layout>
            <div style={{textAlign: "center", height: "70vh"}}>
            <h1>404: Not Found</h1>
            <p>ページが見つかりません。</p>
            </div>
        </Layout>
    )
}

export default NotFoundPage
// contact.js(#5-j2)

import * as React from "react"
import Layout from "../components/layout"
import * as style from "../styles/contact.module.scss"

const Contact = () => {
    return (
        <Layout>
            <div className={style.wrapper}>
                <div className={style.container}>
                    <h1>Contact</h1>
                    <p>お気軽にご連絡ください</p>
                    <form method="post" netlify-honeypot="bot-field" data-netlify="true" name="contact">    // 書き換え
                        <input type="hidden" name="form-name" value="contact"/> // 追加
                        <label htmlFor="name">お名前</label>  
                        <input type="text" name="name" id="name" required/>
                        <label htmlFor="email">メールアドレス</label>
                        <input type="email" name="email" id="email" required/>
                        <label htmlFor="textarea">ご用件</label>
                        <textarea name="message" rows="10" id="textarea" required></textarea>
                        <button type="submit">送信</button>
                    </form> 
                </div>
            </div>
        </Layout>
    )
}

export default Contact
// contact.js(#5-j3)

...

<h1>Contact</h1>
<p>お気軽にご連絡ください</p>
<form method="post" netlify-honeypot="bot-field" data-netlify="true" name="contact" action="/success">    // 追加            
    <input type="hidden" name="form-name" value="contact"/>

...
(#5-s2)

src
.
.
├── pages 
│           ├── 404.js    
│           ├── blog.js
│           ├── contact.js  
│           ├── index.js
│           └── success.js    ←追加
.
.
// success.js(#5-j4)

import * as React from "react"
import Layout from "../components/layout"

const Success = () => {
    return (
        <Layout>
            <div style={{textAlign: "center", height: "70vh"}}>
            <h1>ご連絡ありがとうございます</h1>
            <p>2営業日以内にご返信いたします。</p>
            </div>
        </Layout>
    )
}

export default Success
// gatsby-config.js(#5-j5)

...

{
    resolve: `gatsby-plugin-manifest`,
    options: {
    name: `gatsby-starter-default`,
    short_name: `starter`,
    start_url: `/`,
    background_color: `#663399`,
    // This will impact how browsers show your PWA/website
    // https://css-tricks.com/meta-theme-color-and-trickery/
    // theme_color: `#663399`,
    display: `minimal-ui`,
    icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
    },
},

...
// gatsby-config.js(#5-j6)

...

       icon: `src/images/logo.png`, // This path is relative to the root of the site.
      },
    },
    // this (optional) plugin enables Progressive Web App + Offline functionality
    // To learn more, visit: https://gatsby.dev/offline
    // `gatsby-plugin-offline`,
  ],
}
// gatsby-node.js(#5-j7)

...

result.data.allMarkdownRemark.edges.forEach(({ node }) => {
        createPage({
            path: node.fields.slug,
            component: path.resolve(`./src/templates/single-blog.js`),
            context: {
                slug: node.fields.slug,
            },
        })
})

...
// blog.js(#5-j8)

...

{data.allMarkdownRemark.edges.map((singleBlog, index) => (
        <div className={style.blogCard} key={index}>                            
            <div className={style.textContainer}>
                <h3>{singleBlog.node.frontmatter.title}</h3>
                <p>{singleBlog.node.frontmatter.excerpt}</p>
                <p>{singleBlog.node.frontmatter.date}</p>
                <Link to={`/blog${singleBlog.node.fields.slug}`}>Read More</Link>       // 変更
            </div>
            <GatsbyImage image={singleBlog.node.frontmatter.image.childImageSharp.gatsbyImageData} alt="card-image" className={style.cardImg} />
        </div>
    )
)}

...
// gatsby-config.js(#5-j9)

module.exports = {
  siteMetadata: {
    defaultTitle: `Abe Hiroki Portfolio`,
    defaultDescription: `JavaScriptを専門とするAbe Hirokiのポートフォリオサイトです。`,
    defaultImage: "src/image/social-card.png",
    siteUrl: "https://gatsbybook-portfolio-site.netlify.app",
  },
  plugins: [

...
(#5-s3)

src
.
.
├── components 
│           ├── footer.js    
│           ├── header.js
│           ├── layout.js  
│           └── seo.js    ←追加
.
.
// seo.js(#5-j10)

import * as React from "react"
import { Helmet } from "react-helmet"
import { useLocation } from "@reach/router"
import { useStaticQuery, graphql } from "gatsby"

const SEO = ({ title, description }) => {
    const { pathname } = useLocation()
    const { site } = useStaticQuery(query)
    const {
        defaultTitle,
        defaultDescription,
        defaultImage,
        siteUrl,
    } = site.siteMetadata
    const seo = {
        title: title || defaultTitle,
        description: description || defaultDescription,
        image: defaultImage,
        url: `${siteUrl}${pathname}`,
        canonical: `${siteUrl}${pathname}`
    }
    return (
        <Helmet>
            <html lang="ja" />
            <meta name="viewport" content="initial-scale=1.0, width=device-width" /> 
            <meta charSet="utf-8" />

            <title>{title}</title>
            <meta name="description" content={description} />
            <meta name="image" content={defaultImage} />   
            <link rel="canonical" href={seo.canonical} />

            {seo.title && <meta property="og:title" content={seo.title} />}
            {seo.description && <meta property="og:description" content={seo.description} />}
            {seo.url && <meta property="og:url" content={seo.url} />}
            {seo.image && <meta property="og:image" content={seo.image} />}
        </Helmet>
    )
}

export default SEO

const query = graphql`
  query SEO {
    site {
      siteMetadata {
        defaultTitle
        defaultDescription
        defaultImage
        siteUrl
      }
    }
  }
`
// index.js(#5-j11)

import * as React from "react"
import { Link } from "gatsby"
import { StaticImage } from "gatsby-plugin-image"
import Layout from "../components/layout"
import Seo from "../components/seo"     // 追加
import JSLogo from "../images/javascript.svg"
import ReactLogo from "../images/react.svg"
import GatsbyLogo from "../images/gatsby.svg"
import NextLogo from "../images/next.svg"
import * as style from "../styles/index.module.scss"

const Index = () => {
    return (
        <Layout>
            <Seo title="Abe Hiroki" description="Abe Hirokiのポートフォリオサイトです" />     // 追加
            <div className={style.hero}>

...
// blog.js(#5-j12)

import * as React from "react"
import { graphql, Link } from "gatsby"
import { GatsbyImage } from "gatsby-plugin-image"
import Layout from "../components/layout"
import Seo from "../components/seo"     // 追加
import * as style from "../styles/blog.module.scss"

const Blog = ({ props }) => {
    return (
        <Layout>
            <Seo title="ブログ" description="これはブログページです" />     // 追加

...
// single-blog.js(#5-j13)

import * as React from "react"
import { graphql } from "gatsby" 
import { GatsbyImage } from "gatsby-plugin-image"
import Layout from "../components/layout"
import Seo from "../components/seo"     // 追加
import * as style from "../styles/singleBlog.module.scss"
 
const SingleBlog = (props) => {   
    return (
        <Layout>
            <Seo title={props.data.markdownRemark.frontmatter.title} description={props.data.markdownRemark.frontmatter.excerpt} />     // 追加
            <div className={style.hero}>

...
// contact.js(#5-j14)

import * as React from "react"
import Layout from "../components/layout"
import Seo from "../components/seo"     // 追加
import * as style from "../styles/contact.module.scss"

const Contact = () => {
    return (
        <Layout>
            <Seo title="コンタクト" description="これはコンタクトページです" />     // 追加
            <div className={style.wrapper}>

...
// 404.js(#5-j15)

import * as React from "react"
import Layout from "../components/layout"
import Seo from "../components/seo"     // 追加

const NotFoundPage = () => {
    return(
        <Layout>
            <Seo title="ページが見つかりません" description="これは404ページです" />     // 追加
            <div style={{textAlign: "center", height: "70vh"}}>

...
// success.js(#5-j16)

import * as React from "react"
import Layout from "../components/layout"
import Seo from "../components/seo"     // 追加

const Success = () => {
    return (
        <Layout>
            <Seo title="ありがとうございます" description="これはサクセスページです" />     // 追加
            <div style={{textAlign: "center", height: "70vh"}}>

...
// (#5-j17)

const { title, date, image } = props.data.markdownRemark.frontmatter
// (#5-j18)
...

const SingleBlog = ({ data }) => {   
    return (

...
// blog.js(#5-j19)

import * as React from "react"
import { graphql, Link } from "gatsby"
import { GatsbyImage, getImage } from "gatsby-plugin-image"     // 追加
import Layout from "../components/layout"
import Seo from "../components/seo"
import * as style from "../styles/blog.module.scss"

const Blog = ({ data }) => {    // 変更
    return (
        <Layout>
            <Seo title="ブログ" description="これはブログページです" /> 
            <div className={style.wrapper}>
                <div className={style.container}>
                    <h1>Blog</h1>
                    <p>エンジニアの日常生活をお届けします</p>
                    ↓変更
                    {data.allMarkdownRemark.edges.map((singleBlog, index) => {
                        const { title, date, excerpt, image } = singleBlog.node.frontmatter
                        const { slug } = singleBlog.node.fields
                        const img = getImage(image.childImageSharp.gatsbyImageData)
                        return (
                            <div className={style.blogCard} key={index}>                            
                                <div className={style.textContainer}>
                                    <h3>{title}</h3>
                                    <p>{excerpt}</p>
                                    <p>{date}</p>
                                    <Link to={`/blog${slug}`}>Read More</Link> 
                                </div>
                                <GatsbyImage image={img} alt="card-image" className={style.cardImg} />
                            </div>
                        )}
                    )}
                    ↑変更
                </div>
            </div>
        </Layout>
    )
}

export default Blog

...
// single-blog.js(#5-j20)

import * as React from "react"
import { graphql } from "gatsby" 
import { GatsbyImage, getImage } from "gatsby-plugin-image"     // 追加
import Layout from "../components/layout"
import Seo from "../components/seo" 
import * as style from "../styles/singleBlog.module.scss"
 
const SingleBlog = ({ data }) => {         // 変更
    const { title, date, excerpt, image } = data.markdownRemark.frontmatter      // 追加    
    const { html } = data.markdownRemark     // 追加     
    const img = getImage(image.childImageSharp.gatsbyImageData)     // 追加     
    return (
        <Layout>
            <Seo title={title} description={excerpt} />      // 変更
            <div className={style.hero}>
                <GatsbyImage image={img} alt="blog-image" />      // 変更
            </div>
            <div className={style.wrapper}>  
                <div className={style.container}>               
                    <h1>{title}</h1>      // 変更
                    <p>{date}</p>       // 変更
                    <div dangerouslySetInnerHTML={{ __html: html }} />       // 変更
                </div> 
            </div>
        </Layout>                    
    )
}


export default SingleBlog

...
// layout.js(#5-j21)

import * as React from "react"
import Header from "./header"
import Footer from "./footer"
import "../styles/all.scss"

const Layout = ({ children }) => {  // 変更
  return (
    <>
      <Header />
      <main>{children}</main>    // 変更
      <Footer />
    </>
  )
}

export default Layout
// gatsby-node.js(#5-j22)

...

exports.createPages = async ({ graphql, actions }) => {  
    const { createPage } = actions   

    const result = await graphql(`              
        query {
            allMarkdownRemark {
                edges {
                    node {
                        fields {
                            slug
                        }
                    }
                    ↓追加
                    next {
                        frontmatter {
                            title
                        }
                        fields {
                            slug
                        }
                    }
                    previous {
                        fields {
                            slug
                        }
                        frontmatter {
                            title
                        }
                    }
                    ↑追加
                }
            }
        }
    `)
  
    result.data.allMarkdownRemark.edges.forEach(({ node, next, previous }) => {     // 追加
            createPage({
                path: `blog${node.fields.slug}`,
                component: path.resolve(`./src/templates/single-blog.js`),
                context: {
                    slug: node.fields.slug,
                    next,       // 追加
                    previous,   // 追加
                },
            })
    })
}
// single-blog.js(#5-j23)

...

const SingleBlog = ({ data, pageContext }) => {     // 追加
    const { title, date, excerpt, image } = data.markdownRemark.frontmatter

...
// gatsby-node.js(#5-j24)

...

exports.createPages = async ({ graphql, actions }) => {  
    const { createPage } = actions   

    const result = await graphql(`              
        query {
            allMarkdownRemark (sort: { order: ASC, fields: [frontmatter___id] }){ // 追加
                edges {
                    node {
                        fields {
                            slug
                        }
                    }
                    next {
                        frontmatter {
                            title
                        }
                        fields {
                            slug
                        }
                    }
                    previous {
                        fields {
                            slug
                        }
                        frontmatter {
                            title
                        }
                    }
                }
            }
        }
    `)

...
(#5-s4)

src
.
.
├── components 
│           ├── footer.js    
│           ├── header.js
│           ├── layout.js  
│           ├── prevNext.js     ←追加    
│           └── seo.js    
.
.
// prevNext.js(#5-j25)

import * as React from "react"
import { Link } from 'gatsby'
import ArrowLeft from "../images/arrow-left.svg"
import ArrowRight from "../images/arrow-right.svg"
import * as style from "../styles/singleBlog.module.scss"

const PrevNext =({ pageContext }) => {
    const { previous, next } = pageContext
    return (
        <div className={style.pnWrapper}>
            {previous && 
                <Link to={`/blog${previous.fields.slug}`} className={style.linkCard}>
                    <img src={ArrowLeft} alt="arrow-left"/>
                    <h3> {previous.frontmatter.title}</h3>
                </Link>
            }
            {next && 
                <Link to={`/blog${next.fields.slug}`} className={style.linkCard}>
                    <h3>{next.frontmatter.title}</h3>
                    <img src={ArrowRight} alt="arrow-right"/>
                </Link>
            }
        </div>
    )
}

export default PrevNext
// single-blog.js(#5-j26)

import * as React from "react"
import { graphql } from "gatsby" 
import { GatsbyImage, getImage } from "gatsby-plugin-image" 
import Layout from "../components/layout"
import Seo from "../components/seo" 
import PrevNext from "../components/prevNext"       // 追加
import * as style from "../styles/singleBlog.module.scss"
 
const SingleBlog = ({ data, pageContext }) => {   
    const { title, date, excerpt, image } = data.markdownRemark.frontmatter     
    const { html } = data.markdownRemark     
    const img = getImage(image.childImageSharp.gatsbyImageData)     
    return (
        <Layout>
            <Seo title={title} description={excerpt} /> 
            <div className={style.hero}>
                <GatsbyImage image={img} alt="blog-image" />
            </div>
            <div className={style.wrapper}>  
                <div className={style.container}>               
                    <h1>{title}</h1>
                    <p>{date}</p> 
                    <div dangerouslySetInnerHTML={{ __html: html }} /> 
                </div> 
                <PrevNext pageContext={pageContext} />        // 追加
            </div>
        </Layout>                    
    )
}


export default SingleBlog

export const query = graphql`
    query SingleBlogQuery ($slug: String!) {     
        markdownRemark(fields: { slug: { eq: $slug } }) {   
            frontmatter {
                date
                excerpt
                id
                title
                image {
                    childImageSharp {
                        gatsbyImageData(            
                            placeholder: BLURRED
                            formats: [AUTO, WEBP, AVIF] 
                            quality: 90
                            width: 1000
                        )
                    }
                }
            }
            html
        }
    }
`
// gatsby-node.js(#5-j27)

...

    result.data.allMarkdownRemark.edges.forEach(({ node, next, previous }) => {
            createPage({
                path: node.fields.slug,
                component: path.resolve(`./src/templates/single-blog.js`),
                context: {
                    slug: node.fields.slug,
                    next,
                    previous,
                },
            })
    })

    ↓追加
    const blogs = result.data.allMarkdownRemark.edges
    const blogsPerPage = 5
    const numberPages = Math.ceil(blogs.length / blogsPerPage)
    Array.from({ length: numberPages }).forEach((_, i) => {
        createPage({
            path: i === 0 ? `/blog` : `/blog/${i + 1}`,
            component: path.resolve(`./src/templates/blog.js`),
            context: {
                limit: blogsPerPage,
                skip: i * blogsPerPage,
                numberPages,
                currentPage: i + 1,
            },
        })
    })
    ↑追加

}
(#5-s5)

src
.
.
├── templates 
│           ├── blog.js    ←pagesから移動   
│           └── single-blog.js   
.
.
// blog.js(#5-j28)

...

const Blog = ({ data, pageContext }) => {       // 追加
    return (
        <Layout>
            <Seo title="ブログ" description="これはブログページです" />
            <div className={style.wrapper}>


...

export const query = graphql`
    query BlogQuery ($skip: Int!, $limit: Int!) {       // 追加
        allMarkdownRemark(
            sort: {fields: frontmatter___id, order: DESC}
            limit: $limit     // 追加
            skip: $skip       // 追加
        ) {
            edges {
                node {
                    fields {
                        slug
                    }
                    frontmatter {
                        date
                        excerpt
                        id
                        image {
                            childImageSharp {
                                gatsbyImageData(
                                    quality: 90, 
                                    formats: [AUTO, WEBP, AVIF], 
                                    placeholder: BLURRED, 
                                )
                            }
                        }
                        title
                    }
                }
            }
        }
    }
`
(#5-s6)

src
.
.
├── components 
│           ├── footer.js    
│           ├── header.js
│           ├── layout.js  
│           ├── seo.js    
│           ├── pagination.js    ←追加
│           └── prevNext.js    
.
.
// pagination.js(#5-j29)

import * as React from "react"
import { Link } from 'gatsby'
import * as style from "../styles/blog.module.scss"

const Pagination = ({ pageContext }) => {
    const { numberPages } = pageContext
    return (
        <h2 className={style.paginationWrapper}>
            {Array.from({ length: numberPages }, (_, i) => (
                <Link key={`pagination-number${i + 1}`} to={`/blog/${i === 0 ? "" : i + 1}`}>
                {i + 1}
                </Link>
            ))}
        </h2>
    )
}

export default Pagination
// blog.js(#5-j30)

import * as React from "react"
import { graphql, Link } from "gatsby"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
import Layout from "../components/layout"
import Seo from "../components/seo" 
import Pagination from "../components/pagination"   // 追加
import * as style from "../styles/blog.module.scss"  

const Blog = ({ data, pageContext }) => {
    return (
        <Layout>
            <Seo title="ブログ" description="これはブログページです" /> 
            <div className={style.wrapper}>
                <div className={style.container}>
                    <h1>Blog</h1>
                    <p>エンジニアの日常生活をお届けします</p>
                        {data.allMarkdownRemark.edges.map((singleBlog, index) => {
                            const { title, date, excerpt, image } = singleBlog.node.frontmatter
                            const { slug } = singleBlog.node.fields
                            const img = getImage(image.childImageSharp.gatsbyImageData)
                            return (
                                <div className={style.blogCard} key={index}>                            
                                    <div className={style.textContainer}>
                                        <h3>{title}</h3>
                                        <p>{excerpt}</p>
                                        <p>{date}</p>
                                        <Link to={`/blog${slug}`}>Read More</Link> 
                                    </div>
                                    <GatsbyImage image={img} alt="card-image" className={style.cardImg} />
                                </div>
                            )}
                        )}
                </div>
                <Pagination pageContext={pageContext} />   // 追加
            </div>
        </Layout> 
    )
}

export default Blog

export const query = graphql`
    query BlogQuery ($skip: Int!, $limit: Int!) { 
        allMarkdownRemark(
            sort: {fields: frontmatter___id, order: DESC}
            limit: $limit      
            skip: $skip  
        ) {
            edges {
                node {
                    fields {
                        slug
                    }
                    frontmatter {
                        date
                        excerpt
                        id
                        image {
                            childImageSharp {
                                gatsbyImageData(
                                    quality: 90, 
                                    formats: [AUTO, WEBP, AVIF], 
                                    placeholder: BLURRED, 
                                )
                            }
                        }
                        title
                    }
                }
            }
        }
    }
`
// gatsby-config.js(#5-j31)

...

  plugins: [
    `gatsby-plugin-sass`,
    `gatsby-transformer-remark`,
    `gatsby-plugin-react-helmet`,
    `gatsby-plugin-image`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
          name: `blog`,
          path: `${__dirname}/src/data`,
      },
    },
    ↓追加
    {
      resolve: `gatsby-transformer-remark`,
      options: {
        plugins: [
          {
            resolve: `gatsby-remark-images`,
            options: {
                maxWidth: 800,
            },
          },
        ],
      },
    },
    ↑追加
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
...