「はじめてつくるNext.jsサイト」コード

第2章 開発編 その1

(#2-s1)

src
├── pages   
│           ├── api                 ←削除
│           │    └── hello.js       ←削除
│           │
│           ├── _app.js
│           └── index.js   
│
└── styles
            ├── globals.css           
            └── Home.module.css      ←削除  
          
(#2-s2)

src
├── pages   
│           ├── _app.js
│           └── index.js   
│
└── styles         
            └── globals.css  
          
// index.js(#2-j1)

const Index = () => {
    return (

    )
}

export default Index

// index.js(#2-j2)

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

export default Index

// CSS(#2-c1)

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

// HTML
<h1 class="h1-style">こんにちは</h1>

// React
<h1 className="h1-style">こんにちは</h1>
(#2-h2)

// HTML
<h1 style="color: red; letter-spacing: 20px">こんにちは</h1>

// React
<h1 style={{color: "red", letterSpacing: "20px"}}">こんにちは</h1>
(#2-s3)

src
├── pages   
│           ├── _app.js
│           └── index.js   
│
└── styles         
            ├── globals.css 
            └── index.module.css  ←作成 
          
// index.module.css(#2-c2)

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

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

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

export default Index

//index.js(#2-j4)

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

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

export default Index

(#2-s4)

src
├── pages   
│           ├── _app.js
│           ├── blog.js     ←作成
│           └── index.js   
│
└── styles         
            ├── globals.css 
            └── index.module.css   
          
// blog.js(#2-j5)

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

export default Blog

(#2-s5)

src
├── pages   
│           ├── _app.js
│           ├── blog.js    
│           ├── contact.js     ←作成
│           └── index.js   
│
└── styles         
            ├── globals.css 
            └── index.module.css   
          
// contact.js(#2-j6)
 
const Contact = () => {
    return (
        <h1>コンタクトページ</h1>  
    )
}

export default Contact

//index.js(#2-j7)

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

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

export default Index

//index.js(#2-j8)

import Link from 'next/link'   // 追加
import * as style from "../styles/index.module.css"  

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

export default Index

//index.js(#2-j9)

import Link from 'next/link'
import * as style from "../styles/index.module.css"  

const Index = () => {
    return (
        <h1 className={style.h1Text}>こんにちは</h1> 
        <Link><a>Contactページへ移動</a></Link>       // 修正
    )
}

export default Index

//index.js(#2-j10)

import Link from 'next/link'
import * as style from "../styles/index.module.css"  

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

export default Index

//index.js(#2-j11)

import Link from 'next/link'
import * as style from "../styles/index.module.css"  

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

export default Index

(#2-s6)

src
├── data      ←作成
│
├── pages   
│           ├── _app.js
│           ├── blog.js    
│           ├── contact.js     
│           └── index.js   
│
└── styles         
            ├── globals.css 
            └── index.module.css   
          
(#2-s7)

src
├── data
│           └── first-blog.md     ←作成
│
├── pages   
│           ├── _app.js
│           ├── blog.js    
│           ├── contact.js     
│           └── index.js   
│
└── styles         
            ├── globals.css 
            └── index.module.css   
          
(#2-s8)

src
├── data
│           ├── first-blog.md    
│           ├── second-blog.md    ←作成
│           ├── third-blog.md     ←作成
│           ├── fourth-blog.md    ←作成
│           ├── fifth-blog.md     ←作成
│           └── six-blog.md       ←作成
│
├── pages   
│           ├── _app.js
│           ├── blog.js    
│           ├── contact.js     
│           └── index.js   
│
└── styles         
            ├── globals.css 
            └── index.module.css   
        
// next.config.js(#2-j12)

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  // ↓追加
  webpack: function (config) {
    config.module.rules.push({
        test: /\.md$/,
        use: "raw-loader",
    })
    return config
  },
  // ↑追加
}

module.exports = nextConfig
// blog.js(#2-j13)

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

export default Blog

export async function getStaticProps() { }  // 追加

// blog.js(#2-j14)

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

export default Blog

export async function getStaticProps() { 
    return {            // 追加
        props: {},      // 追加
    }                   // 追加
} 

// blog.js(#2-j15)

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

export default Blog

export async function getStaticProps() { 
    const testText = "Next.jsポートフォリオサイト"  // 追加
    return {            
        props: {},      
    }                   
} 

// blog.js(#2-j16)

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

export default Blog

export async function getStaticProps() { 
    const testText = "Next.jsポートフォリオサイト"
    return {            
        props: {
            test: testText     // 追加
        },      
    }                   
} 

// blog.js(#2-j17)

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

export default Blog

export async function getStaticProps() { 
    const testText = "Next.jsポートフォリオサイト"
    return {            
        props: {
            test: testText    
        },      
    }                   
} 

// blog.js(#2-j18)

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

export default Blog

export async function getStaticProps() { 
    const testText = "Next.jsポートフォリオサイト"
    return {            
        props: {
            test: testText    
        },      
    }                   
} 

// blog.js(#2-j19)

...

export async function getStaticProps() { 
    const blogs = ((context) => {  // 追加

    })(require.context('../data', true, /\.md$/))  // 追加
    
    return {            
        props: {
 
        },      
    }                   
} 

// blog.js(#2-j20)

...

export async function getStaticProps() { 
    const blogs = ((context) => {
        const keys = context.keys()     // 追加
        const values = keys.map(context)    // 追加

    })(require.context('../data', true, /\.md$/))
    
    return {            
        props: {
 
        },      
    }                   
} 

// blog.js(#2-j21)

...

export async function getStaticProps() { 
    const blogs = ((context) => {
        const keys = context.keys()  
        const values = keys.map(context) 
        console.log(values)      // 追加

    })(require.context('../data', true, /\.md$/))
    
    return {            
        props: {
 
        },      
    }                   
} 

// blog.js(#2-j22)

...

export async function getStaticProps() { 
    const blogs = ((context) => {
        const keys = context.keys()  
        const values = keys.map(context) 
        console.log(keys)      // 修正

    })(require.context('../data', true, /\.md$/))
    
    return {            
        props: {
 
        },      
    }                   
} 

// blog.js(#2-j23)

import matter from "gray-matter"    // 追加

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

export default Blog

export async function getStaticProps() { 
    const blogs = ((context) => {
        const keys = context.keys()  
        const values = keys.map(context) 
        console.log(keys)   // 削除
        // ↓追加
        const data = keys.map((key, index) => {
            let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
            const value = values[index]
            const document = matter(value.default)
            return {
                frontmatter: document.data,
                slug: slug
            }
        })
        return data
        // ↑追加
    })(require.context('../data', true, /\.md$/))
    
    return {            
        props: {
 
        },      
    }                   
} 

// blog.js(#2-j24)

...

export async function getStaticProps() { 
    const blogs = ((context) => {
        const keys = context.keys()  
        const values = keys.map(context) 
        const data = keys.map((key, index) => {
            let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
            const value = values[index]
            const document = matter(value.default)
            return {
                frontmatter: document.data,
                slug: slug
            }
        })
        return data
    })(require.context('../data', true, /\.md$/))

    console.log(blogs)  // 追加
    
    return {            
        props: {
 
        },      
    }                   
} 

// blog.js(#2-j25)

import matter from "gray-matter"

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

export default Blog

export async function getStaticProps() { 
    const blogs = ((context) => {
        const keys = context.keys()  
        const values = keys.map(context) 
        const data = keys.map((key, index) => {
            let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
            const value = values[index]
            const document = matter(value.default)
            return {
                frontmatter: document.data,
                slug: slug
            }
        })
        return data
    })(require.context('../data', true, /\.md$/))

    console.log(blogs)  // 削除
    
    return {            
        props: {
            blogs: blogs    // 追加
        },      
    }                   
} 

// blog.js(#2-j26)

...

export async function getStaticProps() { 
    
    ...

    })(require.context('../data', true, /\.md$/))
    
    return {            
        props: {
             blogs: JSON.parse(JSON.stringify(blogs))   // 追加
        },      
    }                   
} 

// blog.js(#2-j27)

import matter from "gray-matter"

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

export default Blog

...

// blog.js(#2-j28)

import matter from "gray-matter"

const Blog = (props) => { 
    console.log(props) 
    return (
        <div>
            <h1>ブログページ</h1>  
            {props.blogs.map((blog, index) => 
                <div>       // 追加
                    <h3>{blog.frontmatter.title}</h3>       // 追加
                    <p>{blog.frontmatter.date}</p>       // 追加
                </div>       // 追加
            )}
        </div>
    )
}

export default Blog

...

// blog.js(#2-j29)

import matter from "gray-matter"

const Blog = (props) => { 
    console.log(props) 
    return (
        <div>
            <h1>ブログページ</h1>  
            {props.blogs.map((blog, index) => 
                <div key={index}>       // 追加
                    <h3>{blog.frontmatter.title}</h3>       
                    <p>{blog.frontmatter.date}</p>      
                </div>      
            )}
        </div>
    )
}

export default Blog

...

// blog.js(#2-j30)

import Link from 'next/link'        // 追加
import matter from "gray-matter"

const Blog = (props) => { 
    console.log(props) 
    return (
        <div>
            <h1>ブログページ</h1>  
            {props.blogs.map((blog, index) => 
                <div key={index}>
                    <h3>{blog.frontmatter.title}</h3>
                    <p>{blog.frontmatter.date}</p>
                    <Link href={`/blog/${blog.slug}`}><a>Read More</a></Link>   // 追加
                </div>
            )}
        </div>
    )
}

export default Blog

...

(#2-s9)

src
├── data
│           ├── first-blog.md    
│           ├── second-blog.md    
│           ├── third-blog.md     
│           ├── fourth-blog.md   
│           ├── fifth-blog.md    
│           └── six-blog.md      
│
├── pages   
│           ├── blog        ←追加
│                           └── [slug].js       ←追加 
│           ├── _app.js
│           ├── blog.js    
│           ├── contact.js     
│           └── index.js   
│
└── styles         
            ├── globals.css 
            └── index.module.css   
        
// [slug].js(#2-j31)

const SingleBlog = () => {
    return (
        <h1>記事ページ</h1>  
    )
}

export default SingleBlog

// [slug].js(#2-j32)

const SingleBlog = () => {
    return (
        <h1>記事ページ</h1>  
    )
}

export default SingleBlog

↓追加
export async function getStaticPaths() {
    return {
        paths: 
        fallback: false,
    }
}
↑追加

// [slug].js(#2-j33)

...

export async function getStaticPaths() {
    ↓追加
    const blogSlugs = ((context) => {
        const keys = context.keys()
        const data = keys.map((key, index) => {
          let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
        return slug
    })
    return data
    })(require.context('../../data', true, /\.md$/))
    ↑追加

    return {
        paths: 
        fallback: false,
    }
}

// [slug].js(#2-j34)

...

export async function getStaticPaths() {
    const blogSlugs = ((context) => {
        const keys = context.keys()
        const data = keys.map((key, index) => {
          let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
        return slug
    })
    return data
    })(require.context('../../data', true, /\.md$/))

    const paths = blogSlugs.map((blogSlug) => `/blog/${blogSlug}`)  // 追加

    return {
        paths: paths,  // 追加
        fallback: false,
    }
}

// [slug].js(#2-j35)

export async function getStaticPaths() {
    
    ...

}

↓追加
export async function getStaticProps() {
    return {
      props: {
   
      }
    }
}
↑追加

// [slug].js(#2-j36)

import matter from "gray-matter"       // 追加

const SingleBlog = () => {
    return (
        <h1>記事ページ</h1>  
    )
}

export default SingleBlog

export async function getStaticPaths() {
    const blogSlugs = ((context) => {
        const keys = context.keys()
        const data = keys.map((key, index) => {
          let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
        return slug
    })
    return data
    })(require.context('../../data', true, /\.md$/))

    const paths = blogSlugs.map((blogSlug) => `/blog/${blogSlug}`)  

    return {
        paths: paths,  
        fallback: false,
    }
}

export async function getStaticProps(context) {     // 追加
    ↓追加
    const { slug } = context.params
    const data = await import(`../../data/${slug}.md`)
    const singleDocument = matter(data.default)
    ↑追加
    
    return {
      props: {
   
      }
    }
}

// [slug].js(#2-j37)

...

export async function getStaticProps(context) { 
    const { slug } = context.params
    const data = await import(`../../data/${slug}.md`)
    const singleDocument = matter(data.default)
    
    console.log(singleDocument)   // 追加

    return {
      props: {
   
      }
    }
}

// [slug].js(#2-j38)

...

export async function getStaticPaths() {
    const blogSlugs = ((context) => {
        const keys = context.keys()
        const data = keys.map((key, index) => {
          let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
        return slug
    })
    return data
    })(require.context('../../data', true, /\.md$/))

    const paths = blogSlugs.map((blogSlug) => `/blog/${blogSlug}`)  

    console.log(paths)   // 追加

    return {
        paths: paths,  
        fallback: false,
    }
}

export async function getStaticProps(context) { 
    const { slug } = context.params
    const data = await import(`../../data/${slug}.md`)
    const singleDocument = matter(data.default)
    
    console.log(singleDocument)   // 削除

    return {
      props: {
   
      }
    }
}

// [slug].js(#2-j39)

import matter from "gray-matter"

const SingleBlog = (props) => { // 追加
    console.log(props)  // 追加
    return (
        <h1>記事ページ</h1>  
    )
}

export default SingleBlog

export async function getStaticPaths() {
    const blogSlugs = ((context) => {
        const keys = context.keys()
        const data = keys.map((key, index) => {
          let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
        return slug
    })
    return data
    })(require.context('../../data', true, /\.md$/))

    const paths = blogSlugs.map((blogSlug) => `/blog/${blogSlug}`)  

    console.log(paths)      // 削除

    return {
        paths: paths,  
        fallback: false,
    }
}

export async function getStaticProps(context) {    
    const { slug } = context.params
    const data = await import(`../../data/${slug}.md`)
    const singleDocument = matter(data.default)
    
    return {
      props: {
        frontmatter: singleDocument.data,         // 追加
        markdownBody: singleDocument.content,      // 追加
      }
    }
}

// [slug].js(#2-j40)

import matter from "gray-matter"
import ReactMarkdown from 'react-markdown'  // 追加

const SingleBlog = (props) => {
    console.log(props)      // 削除
    return (
        ↓追加
        <div>               
            <h1>{props.frontmatter.title}</h1>
            <p>{props.frontmatter.date}</p> 
            <ReactMarkdown children={props.markdownBody} />
        </div>  
        ↑追加
    )
}

export default SingleBlog

...

// blog.js(#2-j41)

...

export async function getStaticProps() {
    const blogs = ((context) => {
        const keys = context.keys()
        const values = keys.map(context)
        const data = keys.map((key, index) => {
            let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
            const value = values[index]
            const document = matter(value.default)
            return {
                frontmatter: document.data,
                slug: slug
            }
        })
        return data
    })(require.context('../data', true, /\.md$/))

    ↓追加
    const orderedBlogs = blogs.sort((a, b) => {
        return b.frontmatter.id - a.frontmatter.id
    })
    ↑追加
    return {
      props: {
        blogs: JSON.parse(JSON.stringify(orderedBlogs)) // 修正
      }
    }
}

// blog.js(#2-j42)

import Link from 'next/link'  
import matter from "gray-matter" 

const Blog = (props) => { 
    return (
        <div>
            <h1>ブログページ</h1>  
            {props.blogs.map((blog, index) => 
                <div key={index}>       
                    <h3>{blog.frontmatter.title}</h3>       
                    <p>{blog.frontmatter.date}</p> 
                    <Link href={`/blog/${blog.slug}`}><a>Read More</a></Link>      
                </div>       
            )}
        </div>
    )
}

export default Blog

export async function getStaticProps() { 
    const blogs = ((context) => {
        const keys = context.keys()  
        const values = keys.map(context) 
        const data = keys.map((key, index) => {
            let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
            const value = values[index]
            const document = matter(value.default)
            return {
                frontmatter: document.data,
                slug: slug
            }
        })
        return data
    })(require.context('../data', true, /\.md$/))

    const orderedBlogs = blogs.sort((a, b) => {
        return b.frontmatter.id - a.frontmatter.id
    })
    
    return {            
        props: {
            blogs: JSON.parse(JSON.stringify(orderedBlogs)) 
        },      
    }                   
} 
// [slug].js(#2-j43)

import matter from "gray-matter"  
import ReactMarkdown from 'react-markdown' 

const SingleBlog = (props) => { 
    return (
        <div>               
            <h1>{props.frontmatter.title}</h1>
            <p>{props.frontmatter.date}</p> 
            <ReactMarkdown>{props.markdownBody}</ReactMarkdown>
        </div>    
    )
}

export default SingleBlog

export async function getStaticPaths() {
    const blogSlugs = ((context) => {
        const keys = context.keys()
        const data = keys.map((key, index) => {
          let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
        return slug
    })
    return data
    })(require.context('../../data', true, /\.md$/))

    const paths = blogSlugs.map((blogSlug) => `/blog/${blogSlug}`)  

    return {
        paths: paths,  
        fallback: false,
    }
}

export async function getStaticProps(context) {
    const { slug } = context.params
    const data = await import(`../../data/${slug}.md`)
    const singleDocument = matter(data.default)
    
    return {
        props: {
            frontmatter: singleDocument.data,
            markdownBody: singleDocument.content,
        }
    }
}

第3章 開発編 その2

(#3-s1)

next-potfolio
├── .next
│
├── node_modules
│
├── public
│           ├── images                           ←追加
│           │           ├── arrow-left.svg       ←追加
│           │           ├── arrow-right.svg      ←追加
│           │           ├── facebook.svg         ←追加
│           │           ├── favicon.ico          ←追加
│           │           ├── 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          ←追加
│           │ 
│           ├── favicon.ico
│           └── vercel.svg
│
├── src
│           ├── data
│           │           ├── first-blog.md     ←置き換え
│           │           ├── second-blog.md    ←置き換え
│           │           ├── third-blog.md     ←置き換え
│           │           ├── fourth-blog.md    ←置き換え
│           │           ├── fifth-blog.md     ←置き換え
│           │           └── six-blog.md       ←置き換え
│           │ 
│           ├── pages
│           │ 
│           └── styles
│                       ├── all.scss               ←追加
│                       ├── blog.module.scss       ←追加
│                       ├── common.module.scss     ←追加
│                       ├── contact.module.scss    ←追加
│                       ├── globals.css             
│                       ├── index.module.css      
│                       ├── index.module.scss      ←追加
│                       ├── singleBlog.module.scss   ←追加
│                       └── variables.module.scss    ←追加
│
│
├── .gitignore
.
.
.

// index.js(#3-j1)

import Link from 'next/link'
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><img src="/images/javascript.svg" alt="javascript"/><span>JavaScript / 10 years</span></div>
                        <div><img src="/images/react.svg"alt="react"/><span>React / 5 years</span></div>
                        <div><img src="/images/gatsby.svg" alt="gatsby"/><span>Gatsby / 3 years</span></div>
                        <div><img src="/images/next.svg" alt="next"/><span>Next.JS / 3 years</span></div>
                    </div>
                </div>
                <div>
                    <Link href="/contact"><a>Make It Happen!</a></Link>
                </div>
            </div>
        </>
    )
}

export default Index

// index.js(#3-j2)

import Link from 'next/link'
import Image from 'next/image'      // 追加

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....</p>
                    </div>
                    <Image src="/images/profile.jpg" alt="hero" height={1195} width={1000} quality={90} />   // 追加
                </div>
                <div>
                    <h2>Skills</h2>

...

// index.js(#3-j3)

import Link from 'next/link'
import Image from 'next/image'

const Index = () => {
    return (
        <>
            <div>
                <Image src="/images/index-hero.jpg" alt="hero" layout="fill" objectFit="cover" quality={90} />  // 追加
                <div>
                    <h1>I'm Abe Hiroki!</h1>
                    <h3>JavaScript Developer</h3>
                </div>
            </div>
            <div>
                <div>
                    <div>
                        <h2>JavaScript Nerd</h2>

        ...

// index.js(#3-j4)

import Link from 'next/link' 
import Image from 'next/image'  

const Index = () => {
  return (
      <>
          <div>
              <Image src="/images/index-hero.jpg" alt="hero" layout="fill" objectFit="cover" quality={90} />
              <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>
                  <Image src="/images/profile.jpg" alt="hero" height={1195} width={1000} quality={90} /> 
              </div>
              <div>
                  <h2>Skills</h2>
                  <div>
                      <div><img src="/images/javascript.svg" alt="javascript"/><span>JavaScript / 10 years</span></div>
                      <div><img src="/images/react.svg"alt="react"/><span>React / 5 years</span></div>
                      <div><img src="/images/gatsby.svg" alt="gatsby"/><span>Gatsby / 3 years</span></div>
                      <div><img src="/images/next.svg" alt="next"/><span>Next.JS / 3 years</span></div>
                  </div>
              </div>
              <div>
                  <Link href="/contact"><a>Make It Happen!</a></Link>
              </div>
          </div>
      </>
  )
}

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

import Link from 'next/link'
import Image from 'next/image'
import matter from "gray-matter"

const Blog = (props) => {
  return (
      <>
          <div>
            <div>
              <h1>Blog</h1>
              <p>エンジニアの日常生活をお届けします</p>
              {props.blogs.map((blog, index) => {
                  return(
                      <div key={index}>                            
                          <div>
                              <h3>{blog.frontmatter.title}</h3>
                              <p>{blog.frontmatter.excerpt}</p>
                              <p>{blog.frontmatter.date}</p>
                              <Link href={`/blog/${blog.slug}`}><a>Read More</a></Link>
                          </div>
                          <div>
                              <Image src={blog.frontmatter.image} alt="card-image" height={300} width={1000} quality={90} />
                          </div>  
                      </div>
                  )}
              )}
              </div>
          </div>
      </>
  )
}

export default Blog

...

// [slug].js(#3-j6)

import Image from 'next/image'
import matter from "gray-matter"
import ReactMarkdown from 'react-markdown'

const SingleBlog = (props) => {
    return (
      <>
          <div>
              <Image src={props.frontmatter.image} alt="blog-image" height="500" width="1000" />
          </div>
          <div>  
              <div>               
                  <h1>{props.frontmatter.title}</h1>
                  <p>{props.frontmatter.date}</p> 
                  <ReactMarkdown children={props.markdownBody} />
              </div> 
          </div>
      </> 
    )
}

export default SingleBlog

...

// contact.js(#3-j7)

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    ←追加
│
├── data
├── pages
└── styles

// header.js(#3-j8)

import Link from 'next/link'
import Image from 'next/image'

const Header = () => {
  return (
     <header>
       <div>
          <div>
            <Link href="/">
              <a> 
                <Image src="/images/logo.png" alt="logo" width={50} height={50} />
              </a>
            </Link>
            <ul>
              <li><Link href="/blog"><a>Blog</a></Link></li>
              <li><Link href="/contact"><a>Contact</a></Link></li>
            </ul>
          </div>
        </div>
    </header>
  )
}

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

import Link from 'next/link'

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

export default Footer
(#3-s3)

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

import Header from "./header"
import Footer from "./footer"

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

export default Layout

// index.js(#3-j11)

import Link from 'next/link'
import Image from 'next/image'
import Layout from "../components/layout"  // 追加

const Index = () => {
    return (
        <Layout>   // 置き換え
                <div>
                    <Image src="/images/index-hero.jpg" alt="hero" layout="fill" objectFit="cover" quality={90} />
                    <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>
                        <Image src="/images/profile.jpg" alt="hero" height={1195} width={1000} quality={90} />
                    </div>
                    <div>
                        <h2>Skills</h2>
                        <div>
                            <div><img src="/images/javascript.svg" alt="javascript"/><span>JavaScript / 10 years</span></div>
                            <div><img src="/images/react.svg"alt="react"/><span>React / 5 years</span></div>
                            <div><img src="/images/gatsby.svg" alt="gatsby"/><span>Gatsby / 3 years</span></div>
                            <div><img src="/images/next.svg" alt="next"/><span>Next.JS / 3 years</span></div>
                        </div>
                    </div>
                    <div>
                        <Link href="/contact">Make It Happen!</Link>
                    </div>
                </div>
        </Layout>   // 置き換え
    )
}
export default Index

// layout.js(#3-j12)

import Header from "./header"
import Footer from "./footer"

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

export default Layout

// blog.js(#3-j13)

import Link from 'next/link'
import Image from 'next/image'
import matter from "gray-matter"
import Layout from "../components/layout"       // 追加

const Blog = (props) => {
  return (
      <Layout>  // 置き換え
          <div>
            <div>
              <h1>Blog</h1>
              <p>エンジニアの日常生活をお届けします</p>
              {props.blogs.map((blog, index) => {
                  return(
                      <div key={index}>                            
                          <div>
                              <h3>{blog.frontmatter.title}</h3>
                              <p>{blog.frontmatter.excerpt}</p>
                              <p>{blog.frontmatter.date}</p>
                              <Link href={`/blog/${blog.slug}`}><a>Read More</a></Link>
                          </div>
                          <div>
                              <Image src={blog.frontmatter.image} alt="card-image" height={300} width={1000} quality={90} />
                          </div>  
                      </div>
                  )}
              )}
              </div>
          </div>
      </Layout>  // 置き換え
  )
}

export default Blog

...

// [slug].js(#3-j14)

import Image from 'next/image'
import matter from "gray-matter"
import ReactMarkdown from 'react-markdown'
import Layout from "../../components/layout"    // 追加

const SingleBlog = (props) => {
    return (
      <Layout>    // 置き換え
          <div>
              <Image src={props.frontmatter.image} alt="blog-image" height="500" width="1000" />
          </div>
          <div>  
              <div>               
                  <h1>{props.frontmatter.title}</h1>
                  <p>{props.frontmatter.date}</p> 
                  <ReactMarkdown children={props.markdownBody} />
              </div> 
          </div>
      </Layout>    // 置き換え 
    )
}

export default SingleBlog

...

// contact.js(#3-j15)

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

// index.js(#3-j16)

import Link from 'next/link'
import Image from 'next/image'
import Layout from "../components/layout"
import * as style from "../styles/index.module.scss"  // 追加

...

// blog.js(#3-j17)

import Link from 'next/link'
import Image from 'next/image'
import matter from "gray-matter"
import Layout from "../components/layout"
import * as style from "../styles/blog.module.scss"  // 追加

...

// [slug].js(#3-j18)

import Image from 'next/image'
import matter from "gray-matter"
import ReactMarkdown from 'react-markdown'
import Layout from "../../components/layout"
import * as style from "../../styles/singleBlog.module.scss"  // 追加

...

// contact.js(#3-j19)

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

...

// header.js(#3-j20)

import Link from 'next/link'
import Image from 'next/image'
import * as style from "../styles/common.module.scss"  // 追加

...

// footer.js(#3-j21)

import Link from 'next/link'
import * as style from "../styles/common.module.scss"  // 追加

...

// _app.js(#3-j22)

import '../styles/globals.css'  // 削除
import "../styles/all.scss"  // 追加

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp

// index.js(#3-j23)

import Link from 'next/link' 
import Image from 'next/image' 
import Layout from "../components/layout" 
import * as style from "../styles/index.module.scss"

const Index = () => {
  return (
      <Layout>
          <div className={style.hero}>
              <Image src="/images/index-hero.jpg" alt="hero" layout="fill" objectFit="cover" quality={90} />
              <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>
                  <Image src="/images/profile.jpg" alt="hero" height={1195} width={1000} quality={90} /> 
              </div>
              <div className={style.skills}>
                  <h2>Skills</h2>
                  <div className={style.skillsContainer}>
                      <div><img src="/images/javascript.svg" alt="javascript"/><span>JavaScript / 10 years</span></div>
                      <div><img src="/images/react.svg"alt="react"/><span>React / 5 years</span></div>
                      <div><img src="/images/gatsby.svg" alt="gatsby"/><span>Gatsby / 3 years</span></div>
                      <div><img src="/images/next.svg" alt="next"/><span>Next.JS / 3 years</span></div>
                  </div>
              </div>
              <div className={style.ctaButton}>
                  <Link href="/contact"><a>Make It Happen!</a></Link>
              </div>
          </div>
      </Layout>
  )
}

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

import Link from 'next/link'
import Image from 'next/image'
import matter from "gray-matter"
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.blogs.map((blog, index) => {
                  return(
                      <div key={index} className={style.blogCard}>                            
                          <div className={style.textContainer}>
                              <h3>{blog.frontmatter.title}</h3>
                              <p>{blog.frontmatter.excerpt}</p>
                              <p>{blog.frontmatter.date}</p>
                              <Link href={`/blog/${blog.slug}`}><a>Read More</a></Link>
                          </div>
                          <div className={style.cardImg}>
                              <Image src={blog.frontmatter.image} alt="card-image" height={300} width={1000} quality={90} />
                          </div>  
                      </div>
                  )}
              )}
              </div>
          </div>
      </Layout>
  )
}

export default Blog

export async function getStaticProps() { 
    const blogs = ((context) => {
        const keys = context.keys()     
        const values = keys.map(context)
        const data = keys.map((key, index) => {
            let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
            const value = values[index]
            const document = matter(value.default)
            return {
                frontmatter: document.data,
                slug: slug
            }
        })
        return data
    })(require.context('../data', true, /\.md$/))

    const orderedBlogs = blogs.sort((a, b) => {
        return b.frontmatter.id - a.frontmatter.id
    })
    
    return {            
        props: {
            blogs: JSON.parse(JSON.stringify(orderedBlogs))
        },      
    }  
}
// [slug].js(#3-j25)

import Image from 'next/image'
import matter from "gray-matter"
import ReactMarkdown from 'react-markdown'
import Layout from "../../components/layout"
import * as style from "../../styles/singleBlog.module.scss"

const SingleBlog = (props) => {
    return (
      <Layout>
          <div className={style.hero}>
              <Image src={props.frontmatter.image} alt="blog-image" height="500" width="1000" />
          </div>
          <div className={style.wrapper}>  
              <div className={style.container}>               
                  <h1>{props.frontmatter.title}</h1>
                  <p>{props.frontmatter.date}</p> 
                  <ReactMarkdown children={props.markdownBody} />
              </div> 
          </div>
      </Layout> 
    )
}

export default SingleBlog

export async function getStaticPaths() {
    const blogSlugs = ((context) => {
        const keys = context.keys()
        const data = keys.map((key, index) => {
          let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
        return slug
    })
    return data
    })(require.context('../../data', true, /\.md$/))

    const paths = blogSlugs.map((blogSlug) => `/blog/${blogSlug}`) 

    return {
        paths: paths,
        fallback: false,
    }
}

export async function getStaticProps(context) {
    const { slug } = context.params
    const data = await import(`../../data/${slug}.md`)
    const singleDocument = matter(data.default)

    return {
      props: {
        frontmatter: singleDocument.data,         
        markdownBody: singleDocument.content, 
      }
    }
}
// contact.js(#3-j26)

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-j27)

import Link from 'next/link'
import Image from 'next/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 href="/">
                      <a> 
                        <Image src="/images/logo.png" alt="logo" width={50} height={50} />
                      </a>
                  </Link>
                  <ul>
                      <li><Link href="/blog"><a>Blog</a></Link></li>
                      <li><Link href="/contact"><a>Contact</a></Link></li>
                  </ul>
              </div>
          </div>
      </header>
  )
}

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

import Link from 'next/link'
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="/images/github.svg" alt="logo"/></a>
                <a href="https://www.google.com/"><img src="/images/linkedin.svg" alt="logo"/></a>
                <a href="https://www.google.com/"><img src="/images/twitter.svg" alt="logo"/></a>
                <a href="https://www.google.com/"><img src="/images/facebook.svg" alt="logo"/></a>
                <hr/>
                <Link href="/blog"><a>Blog</a></Link>
                <Link href="/contact"><a>Contact</a></Link>
                <p>©{new Date().getFullYear()} Abe Hiroki</p>
            </div>
        </footer>
    )
}

export default Footer

第5章 ブラッシュアップ

(#5-s1)

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

import Layout from "../components/layout"

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

export default NotFoundPage

// contact.js(#5-j2)

...

const Contact = () => {
    return (
        <Layout>
            <Seo title="コンタクト" description="これはコンタクトページです" />
            <div className={style.wrapper}>
                <div className={style.container}>
                    <h1>Contact</h1>
                    <p>お気軽にご連絡ください</p>
                    <form action="https://formspree.io/f/mlefwfbp" method="POST">   // 置き換え
                        <label htmlFor="name">お名前</label>  
                        <input type="text" name="name" id="name" required/>
                        <label htmlFor="email">メールアドレス</label>

...

// (#5-j3)

const { title, date, excerpt, image } = blog.frontmatter
// blog.js(#5-j4)

const Blog = ({ blogs }) => {
    
    ...

// blog.js(#5-j5)

import Link from 'next/link'
import Image from 'next/image'
import matter from "gray-matter"
import Layout from "../components/layout"
import * as style from "../styles/blog.module.scss" 

const Blog = ({ blogs }) => {
  return (
      <Layout>
          <div className={style.wrapper}>
            <div className={style.container}>
              <h1>Blog</h1>
              <p>エンジニアの日常生活をお届けします</p>
              {blogs.map((blog, index) => {
                  const { title, date, excerpt, image } = blog.frontmatter
                  return(
                      <div key={index} className={style.blogCard}>                            
                          <div className={style.textContainer}>
                              <h3>{title}</h3>
                              <p>{excerpt}</p>
                              <p>{date}</p>
                              <Link href={`/blog/${blog.slug}`}><a>Read More</a></Link>
                          </div>
                          <div className={style.cardImg}>
                              <Image src={image} alt="card-image" height={300} width={1000} quality={90} />
                          </div>  
                      </div>
                  )}
              )}
              </div>
          </div>
      </Layout>
  )
}

export default Blog

...

// [slug].js(#5-j6)

import Image from 'next/image'
import matter from "gray-matter"
import ReactMarkdown from 'react-markdown'
import Layout from "../../components/layout" 
import * as style from "../../styles/singleBlog.module.scss"

const SingleBlog = ({ frontmatter, markdownBody }) => {
    const { title, date, image } = frontmatter
    return (
        <Layout>
            <div className={style.hero}>
                <Image src={image} alt="blog-image" height="500" width="1000" />
            </div>
            <div className={style.wrapper}>  
                <div className={style.container}>               
                    <h1>{title}</h1>
                    <p>{date}</p> 
                    <ReactMarkdown>{markdownBody}</ReactMarkdown>
                </div> 
            </div>
        </Layout> 
    )
}

export default SingleBlog

...

// layout.js(#5-j7)

import Header from "./header"
import Footer from "./footer"

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

export default Layout
(#5-s2)

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

import { useRouter } from 'next/router'
import { Helmet } from "react-helmet"

const SEO = ({ title, description }) => {
    const router = useRouter()
    const baseUrl = "http://localhost:3000"
    const currentUrl = baseUrl + router.pathname
    const defaultImage = `${baseUrl}/images/social-card.png`
    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} key="description" />
            <meta name="image" content={defaultImage} key="image" />   
            <link rel="canonical" href={currentUrl} key="canonical" />

            <meta property="og:title" content={title} key="ogtitle" />
            <meta property="og:description" content={description} key="ogdescription" />
            <meta property="og:image" content={defaultImage} key="ogimage" />
            <meta property="og:url" content={currentUrl} key="ogurl" />
        </Helmet>
    )
}

export default SEO

// index.js(#5-j9)

import Link from 'next/link'
import Image from 'next/image'
import Layout from "../components/layout"
import Seo from "../components/seo" // 追加
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-j10)

import Link from 'next/link'
import Image from 'next/image'
import matter from "gray-matter"
import Layout from "../components/layout"
import Seo from "../components/seo"  // 追加
import * as style from "../styles/blog.module.scss"

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

...

// [slug].js(#5-j11)

import Image from 'next/image'
import matter from "gray-matter"
import ReactMarkdown from 'react-markdown'
import Layout from "../../components/layout" 
import Seo from "../../components/seo"     // 追加
import * as style from "../../styles/singleBlog.module.scss"

const SingleBlog = ({ frontmatter, markdownBody }) => {
    const { title, date, excerpt, image } = frontmatter    // excerptを追加
    return (
      <Layout>
          <Seo title={title} description={excerpt} />    // 追加
          <div className={style.hero}>

...

// contact.js(#5-j12)

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}>
                <div className={style.container}>   

...

// 404.js(#5-j13)

import Layout from "../components/layout"
import Seo from "../components/seo"     // 追加

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

...

// seo.js(#5-j14)

...

            <meta property="og:image" content={defaultImage} key="ogimage" />
            <meta property="og:url" content={currentUrl} key="ogurl" />

            <link rel="shortcut icon" href="/images/favicon.ico" />     // 追加
        </Helmet>
    )
}

export default SEO

(#5-s3)

src
.
.
├── styles
│  
└── utils         ← 作成   
            └── mdQueries.js  ← 作成  

// mdQueries.js(#5-j15)

import matter from 'gray-matter'

export async function getAllBlogs() {
    const blogs = ((context) => {
        const keys = context.keys()     
        const values = keys.map(context)
        const data = keys.map((key, index) => {
            let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
            const value = values[index]
            const document = matter(value.default)
            return {
                frontmatter: document.data,
                slug: slug
            }
        })
        return data
    })(require.context('../data', true, /\.md$/))

    const orderedBlogs = blogs.sort((a, b) => {
        return b.frontmatter.id - a.frontmatter.id
    })

    return {
        orderedBlogs: JSON.parse(JSON.stringify(orderedBlogs))
    }
}

export async function getSingleBlog(context) {
    const { slug } = context.params
    const data = await import(`../data/${slug}.md`)
    const singleDocument = matter(data.default)

    return {
        singleDocument: singleDocument
    }
}

// blog.js(#5-j16)

import Link from 'next/link'
import Image from 'next/image'
import matter from "gray-matter"    // 削除
import Layout from "../components/layout"
import Seo from "../components/seo"
import * as style from "../styles/blog.module.scss"
import { getAllBlogs } from "../utils/mdQueries"    // 追加

...

export default Blog

export async function getStaticProps() {
    const { orderedBlogs } = await getAllBlogs()   // 置き換え

    return {
      props: {
        blogs: orderedBlogs  // 置き換え
      }
    }
}

// [slug].js(#5-j17)

import Image from 'next/image'
import matter from "gray-matter"   // 削除
import ReactMarkdown from 'react-markdown'
import Layout from "../../components/layout"
import Seo from "../../components/seo"
import * as style from "../../styles/singleBlog.module.scss"
import { getAllBlogs, getSingleBlog } from "../../utils/mdQueries"  // 追加

...

export default SingleBlog

export async function getStaticPaths() {
    const { orderedBlogs } = await getAllBlogs()    // 置き換え
    const paths = orderedBlogs.map((orderedBlog) => `/blog/${orderedBlog.slug}`)    // 修正

    return {
        paths: paths,  
        fallback: false,
    }
}

export async function getStaticProps(context) {    
    const { singleDocument } = await getSingleBlog(context)   // 置き換え

    return {
      props: {
        frontmatter: singleDocument.data,         
        markdownBody: singleDocument.content,  
      }
    }
}

// [slug].js(#5-j18)

...

export async function getStaticProps(context) {    
    const singleDocument = await getSingleBlog(context)   

    ↓追加
    const { orderedBlogs } = await getAllBlogs()
    const prev = orderedBlogs.filter(orderedBlog => orderedBlog.frontmatter.id === singleDocument.data.id - 1)
    const next = orderedBlogs.filter(orderedBlog => orderedBlog.frontmatter.id === singleDocument.data.id + 1)
    ↑追加

    return {
      props: {
        frontmatter: singleDocument.data,     
        markdownBody: singleDocument.content,  
        prev: prev,  // 追加
        next: next,  // 追加
      }
    }
}

(#5-s4)

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

import Link from 'next/link'
import * as style from "../styles/singleBlog.module.scss"

const PrevNext =(props) => {
    return (
        <div className={style.pnWrapper}>
            {0 < props.prev.length && 
                <Link href={`/blog/${props.prev[0].slug}`}>
                    <a className={style.linkCard}>
                        <img src="/images/arrow-left.svg" alt="arrow-left"/>
                        <h3> {props.prev[0].frontmatter.title}</h3>
                    </a>
                </Link>
            }
            {0 < props.next.length && 
                <Link href={`/blog/${props.next[0].slug}`}>
                    <a className={style.linkCard}>
                        <h3>{props.next[0].frontmatter.title}</h3>
                        <img src="/images/arrow-right.svg" alt="arrow-right" className="arrow-right"/>
                    </a>
                </Link>
            }
        </div>
    )
}

export default PrevNext 
// [slug].js(#5-j20)

import Image from 'next/image'
import ReactMarkdown from 'react-markdown'
import Layout from "../../components/layout"
import PrevNext from "../../components/prevNext"
import Seo from "../../components/seo"
import * as style from "../../styles/singleBlog.module.scss"
import { getAllBlogs, getSingleBlog } from "../../utils/mdQueries"

const SingleBlog = ({ frontmatter, markdownBody, prev, next }) => {
    const { title, date, excerpt, image } = frontmatter
    return (
      <Layout>
          <Seo title={title} description={excerpt} />
          <div className={style.hero}>
              <Image src={image} alt="blog-image" height="500" width="1000" />
          </div>
          <div className={style.wrapper}>  
              <div className={style.container}>               
                  <h1>{title}</h1>
                  <p>{date}</p> 
                  <ReactMarkdown>{props.markdownBody}</ReactMarkdown>
              </div> 
              <PrevNext prev={prev} next={next} />
          </div>
      </Layout> 
    )
}

export default SingleBlog

export async function getStaticPaths() {
    const { orderedBlogs } = await getAllBlogs()    
    const paths = orderedBlogs.map((orderedBlog) => `/blog/${orderedBlog.slug}`)   

    return {
        paths: paths,  
        fallback: false,
    }
}

export async function getStaticProps(context) {    
    const { singleDocument } = await getSingleBlog(context)   

    const { orderedBlogs } = await getAllBlogs()
    const prev = orderedBlogs.filter(orderedBlog => orderedBlog.frontmatter.id === singleDocument.data.id - 1)
    const next = orderedBlogs.filter(orderedBlog => orderedBlog.frontmatter.id === singleDocument.data.id + 1)

    return {
      props: {
        frontmatter: singleDocument.data,         
        markdownBody: singleDocument.content,  
        prev: prev,
        next: next,
      }
    }
}
// (#5-j21)

return {
    props: {
        frontmatter: singleDocument.data,     
        markdownBody: singleDocument.content,  
        prev,    // 修正
        next,    // 修正
    }
}

(#5-s5)

src
.
.
├── pages 
│           ├── blog 
│           │         ├── page   ←追加
│           │         │           └── [pagination].js   ←追加
│           │         └── [slug].js
│           │
│           ├── _app.js
│           ├── 404.js  
│           ├── blog.js
│           ├── contact.js
│           └── index.js
.
.
// mdQueries.md(#5-j22)

import matter from 'gray-matter'

export const blogsPerPage = 5       // 追加

export async function getAllBlogs() {
    const blogs = ((context) => {
        const keys = context.keys()     
        const values = keys.map(context)
        const data = keys.map((key, index) => {
            let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
            const value = values[index]
            const document = matter(value.default)
            return {
                frontmatter: document.data,
                slug: slug
            }
        })
        return data
    })(require.context('../data', true, /\.md$/))

    const orderedBlogs = blogs.sort((a, b) => {
        return b.frontmatter.id - a.frontmatter.id
    })

    const numberPages = Math.ceil(orderedBlogs.length / blogsPerPage)      // 追加

    return {
        orderedBlogs: JSON.parse(JSON.stringify(orderedBlogs)),  // ,を忘れないように。
        numberPages: numberPages       // 追加
    }
}

...

// [pagination].js(#5-j23)

import Link from 'next/link'
import Image from 'next/image'
import Layout from "../../../components/layout"
import Seo from "../../../components/seo"
import * as style from "../../../styles/blog.module.scss"
import { getAllBlogs } from "../../../utils/mdQueries"

// [pagination].js(#5-j24)

import { getAllBlogs, blogsPerPage } from "../../../utils/mdQueries"  // 追加

// [pagination].js(#5-j25)

...

export default Blog

export async function getStaticPaths() {
    const { numberPages } = await getAllBlogs()

    let paths = []
    Array.from({ length: numberPages }).slice(0, 1).forEach((_, i) => paths.push(`/blog/page/${i + 2}`))

    return {
        paths: paths,  
        fallback: false,
    }
}

export async function getStaticProps() {
    
    ...

// [pagination].js(#5-j26)

...

export async function getStaticProps(context) {       // 追加
    const { orderedBlogs, numberPages } = await getAllBlogs()   // 追加

    const currentPage = context.params.pagination   // 追加
    const limitedBlogs = orderedBlogs.slice((currentPage -1) * blogsPerPage, currentPage * blogsPerPage)    // 追加

    return {
      props: {
        blogs: limitedBlogs,    // 修正
        numberPages: numberPages,    // 追加
      }
    }
}

// blog.js(#5-j27)

import { getAllBlogs, blogsPerPage } from "../utils/mdQueries"  // 追加

...

export async function getStaticProps() { 
    const { orderedBlogs, numberPages } = await getAllBlogs() // 修正

    const limitedBlogs = orderedBlogs.slice(0, blogsPerPage)   // 追加
    
    return {            
        props: {
            blogs: limitedBlogs,  // 修正
            numberPages: numberPages, // 追加
        },      
    }                   
}  

(#5-s6)

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

import Link from 'next/link'
import * as style from "../styles/blog.module.scss"

export const Pagination = ({ numberPages }) => {
  return (
    <h2  className={style.paginationWrapper}>
      {Array.from({ length: numberPages }, (_, i) => (
          <Link key={i + 1} href={ i === 0 ? `/blog` : `/blog/page/${i + 1}`}>
            <a>{i + 1}</a>
          </Link>
      ))}
    </h2>
  )
}

export default Pagination

// [pagination].js(#5-j29)

import Link from 'next/link'
import Image from 'next/image'
import Layout from "../../../components/layout"
import Seo from "../../../components/seo"
import Pagination from "../../../components/pagination"     // 追加
import * as style from "../../../styles/blog.module.scss"
import { getAllBlogs, blogsPerPage } from "../../../utils/mdQueries.js"

const PaginationPage = ({ blogs, numberPages }) => {    // 追加
  return (
   
...

                      </div>
                  )}
              )}
              </div>
              <Pagination numberPages={numberPages} />    // 追加
          </div>
      </Layout>

...

// blog.js(#5-j30)

import Link from 'next/link'
import Image from 'next/image'
import Layout from "../components/layout"
import Seo from "../components/seo"
import Pagination from "../components/pagination"   // 追加
import * as style from "../styles/blog.module.scss"
import { getAllBlogs, blogsPerPage } from "../utils/mdQueries"

const Blog = ({ blogs, numberPages }) => {   // 追加
  return (

...

                      </div>
                  )}
              )}
              </div>
              <Pagination numberPages={numberPages} />    // 追加
          </div>
      </Layout>

...

// mdQueries.js(#5-j31)

import matter from 'gray-matter'

export const blogsPerPage = 5 

export async function getAllBlogs() {
    const blogs = ((context) => {
        const keys = context.keys()     
        const values = keys.map(context)
        const data = keys.map((key, index) => {
            let slug = key.replace(/^.*[\\\/]/, '').slice(0, -3)
            const value = values[index]
            const document = matter(value.default)
            return {
                frontmatter: document.data,
                slug: slug
            }
        })
        return data
    })(require.context('../data', true, /\.md$/))

    const orderedBlogs = blogs.sort((a, b) => {
        return b.frontmatter.id - a.frontmatter.id
    })

    const numberPages = Math.ceil(orderedBlogs.length / blogsPerPage)  

    return {
        orderedBlogs: JSON.parse(JSON.stringify(orderedBlogs)),
        numberPages: numberPages
    }
}

export async function getSingleBlog(context) {
    const { slug } = context.params
    const data = await import(`../data/${slug}.md`)
    const singleDocument = matter(data.default)

    return {
        singleDocument: singleDocument
    }
}
// [pagination].js(#5-j32)

import Link from 'next/link'
import Image from 'next/image'
import Layout from "../../../components/layout"
import Seo from "../../../components/seo"
import Pagination from "../../../components/pagination" 
import * as style from "../../../styles/blog.module.scss"
import { getAllBlogs, blogsPerPage } from "../../../utils/mdQueries"

const PaginationPage = ({ blogs, numberPages }) => {
  return (
      <Layout>
          <Seo title="ブログ" description="これはブログページです" /> 
          <div className={style.wrapper}>
            <div className={style.container}>
              <h1>Blog</h1>
              <p>エンジニアの日常生活をお届けします</p>
              {blogs.map((blog, index) => {
                  const { title, date, excerpt, image } = blog.frontmatter
                  return(
                      <div key={index} className={style.blogCard}>                            
                          <div className={style.textContainer}>
                              <h3>{title}</h3>
                              <p>{excerpt}</p>
                              <p>{date}</p>
                              <Link href={`/blog/${blog.slug}`}><a>Read More</a></Link>
                          </div>
                          <div className={style.cardImg}>
                              <Image src={image} alt="card-image" height={300} width={1000} quality={90} />
                          </div>  
                      </div>
                  )}
              )}
              </div>
              <Pagination numberPages={numberPages} /> 
          </div>
      </Layout>
  )
}

export default PaginationPage

export async function getStaticPaths() {
    const { numberPages } = await getAllBlogs()

    let paths = []
    Array.from({ length: numberPages }).slice(0, 1).forEach((_, i) => paths.push(`/blog/page/${i + 2}`))

    return {
        paths: paths,  
        fallback: false,
    }
}

export async function getStaticProps(context) {       
    const { orderedBlogs, numberPages } = await getAllBlogs()   

    const currentPage = context.params.pagination   
    const limitedBlogs = orderedBlogs.slice((currentPage -1) * blogsPerPage, currentPage * blogsPerPage)    

    return {
      props: {
        blogs: limitedBlogs, 
        numberPages: numberPages,    
      }
    }
}
// blog.js(#5-j33)

import Link from 'next/link'
import Image from 'next/image'
import Layout from "../components/layout"
import Seo from "../components/seo"
import Pagination from "../components/pagination" 
import * as style from "../styles/blog.module.scss" 
import { getAllBlogs, blogsPerPage } from "../utils/mdQueries" 

const Blog = ({ blogs, numberPages }) => {
  return (
      <Layout>
          <Seo title="ブログ" description="これはブログページです" /> 
          <div className={style.wrapper}>
            <div className={style.container}>
              <h1>Blog</h1>
              <p>エンジニアの日常生活をお届けします</p>
              {blogs.map((blog, index) => {
                  const { title, date, excerpt, image } = blog.frontmatter
                  return(
                      <div key={index} className={style.blogCard}>                            
                          <div className={style.textContainer}>
                              <h3>{title}</h3>
                              <p>{excerpt}</p>
                              <p>{date}</p>
                              <Link href={`/blog/${blog.slug}`}><a>Read More</a></Link>
                          </div>
                          <div className={style.cardImg}>
                              <Image src={image} alt="card-image" height={300} width={1000} quality={90} />
                          </div>  
                      </div>
                  )}
              )}
              </div>
              <Pagination numberPages={numberPages} />
          </div>
      </Layout>
  )
}

export default Blog

export async function getStaticProps() {
    const { orderedBlogs, numberPages } = await getAllBlogs() 

    const limitedBlogs = orderedBlogs.slice(0, blogsPerPage)

    return {
      props: {
        blogs: limitedBlogs, 
        numberPages: numberPages,
      }
    }
}
(#5-s7)

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

const data = [
    { name: "Joe Biden", period: "2021-" },
    { name: "Donald Trump", period: "2017-2021" },
    { name: "Barack Obama", period: "2009-2017" },
]

export default function server(req, res){
    res.status(200).json(data)
}