【ビギナー向け】Jamstackサイトの簡単な作り方

blog-hero-imgGatsbyとContentfulを使って、CMS付きのJamstackサイトを作り方を紹介します。

pen-icon2021.12.27

Profile Pic

この記事の筆者:三好アキ(エンジニア)


ウェブデザイナーから『エンジニア』『プログラマー』へ成長したい人、独学で進んでいきたい人を応援しています。 HTMLとCSSの知識だけでアプリ開発を始められる入門書を多数執筆中📕📗👇


ウェブ制作の教本『はじめてつくるReactアプリ』など複数冊を執筆。



React、Next.js、TypeScriptなどのお役立ち情報や実践的コンテンツを、ビギナー向けにかみ砕いて配信中。登録はこちらから → 無料メルマガ登録

JamstackとヘッドレスCMS

本記事はまず最初にGatsbyにマークダウンのブログ機能を実装し、その後、ヘッドレスCMSのContentfulをつなげる方法を紹介します。

ヘッドレスCMSを活用することで、プログラマーやエンジニアではない人でも、コードをさわることなく簡単にブログ記事の作成や編集ができるようになるので、Jamstackサイトのコンテンツ管理(CMS)には多くの場合ヘッドレスCMSが接続されています。

本記事ではホスティングにNetlifyを使います。

記事の後半ではGitHubが必要なので、プッシュができるように設定は済ませておいてください。

流れは次のようになります。


ステップ1:Gatsbyにマークダウンのブログ機能を実装

ステップ2:Contentfulにブログ記事データを保管

ステップ3:ContentfulとGatsbyをつなぐ

ステップ4:ContentfulとNetlifyをつなぐ


ロジック部分の説明だけなので、スタイルは各自お好みで適用してください。

この記事を読んでいる人は、もうすでにJamstackのことを知っていると思いますが、くわしく知りたい方は次の記事を参考にしてください。

ヘッドレスCMSについては次の記事に詳しく書いてあります。

Gatsbyにマークダウンのブログ機能を実装する

まずGatsbyのスターターキットに、マークダウンのブログ機能を追加しましょう。

最初にスターターサイトをダウンロードするので、ターミナルで以下のコマンドを実行します。

npx gatsby new gatsby-starter-default https://github.com/gatsbyjs/gatsby-starter-default

次にブログ記事を一覧表示するblog.js、個別記事を表示するsingleBlog.jsを作ります。

まずはblog.jsから始めます。


├──pages
│       ├── 404.js   
│       ├── blog.js    ←追加
│       ├── index.js  
│       ├── pages-2.js   
│       ├── using-ssr.js  
│       └── using-typescript.tsx 

次のコードを書きます。

// blog.js

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}>              
                    <h2><Link to={`/blog${singleBlog.node.fields.slug}`}>{singleBlog.node.frontmatter.title}</Link></h2>       
                    <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
                        summary
                        id
                        title
                    }
                }
            }
        }
    }
`

singleBlog.jstemplatesフォルダ内に作ります。


├──templates 
│       ├── singleBlog.js    ←追加  
│       └── using-dsg.js

次のコードを書きます。

// singleBlog.js

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
                summary
                id
            }
            html
        }
    }
`

次に、ブログ記事のマークダウンファイルを収納するフォルダをsrc内に作ります。


├──src
│  ├── components    
│  ├── data    ←追加
│  ├── images  
│  ├── pages   
│  └── templates    

data内には6つのマークダウンファイルを作ります。


├── data
│      ├── first.md    
│      ├── second.md  
│      ├── third.md   
│      ├── fourth.md  
│      ├── fifth.md   
│      └── sixth.md    

それぞれのマークダウンファイルには、下のように基本的な情報を書きます。

// first.md

---
id: "1"
title: "記事1"
date: "2021-06-21"
summary: "記事1の要約"
---

1つ目の記事。
// second.md

---
id: "2"
title: "記事2"
date: "2021-06-22"
summary: "記事2の要約"
---

2つ目の記事。
// third.md

---
id: "3"
title: "記事3"
date: "2021-06-23"
summary: "記事3の要約"
---

3つ目の記事。
// fourth.md

---
id: "4"
title: "記事4"
date: "2021-06-24"
summary: "記事4の要約"
---

4つ目の記事。
// fifth.md

---
id: "5"
title: "記事5"
date: "2021-06-25"
summary: "記事5の要約"
---

5つ目の記事。
// sixth.md

---
id: "6"
title: "記事6"
date: "2021-06-26"
summary: "記事6の要約"
---

6つ目の記事。

これでブログのデータと、それを表示する部分ができました。

次は表示するための仕組みづくりです。

ブログ機能に必要なプラグインをインストールしましょう。

npm install gatsby-transformer-remark

これと、デフォルトですでに入っているgatsby-source-filesystemgatsby-config.jsに追加します。

// gatsby-config.js

module.exports = {
  siteMetadata: {
     
      ...

  plugins: [
    `gatsby-plugin-react-helmet`,
    `gatsby-plugin-image`,
    // ⬇ 追加
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `data`,
        path: `${__dirname}/src/data`,
      },
    },
    `gatsby-transformer-remark`,
    // ⬆ 追加
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },
    `gatsby-transformer-sharp`,

    ...

gatsby-node.jsに、上で作ったコンポーネントにマークダウンファイルを流し込むためのコードを追加します。

// gatsby-node.js

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

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: `blog${node.fields.slug}`,
            component: path.resolve(`./src/templates/singleBlog.js`),
            context: {
                slug: node.fields.slug,
            },
        })
    })
}

ここまで加えた変更をすべて保存します。

gatsby developでGatsbyを起動し、http://localhost:8000/blogをブラウザで開くと、ブログ機能が追加されているのがわかります。

次は、ここまでマークダウンファイル上で行っていたブログ記事の作成や編集の作業を、ヘッドレスCMSのContentful上で行えるようにします。

Contentfulにブログ記事データを保管する

次のURLを開き、アカウントを作成します。

https://www.contentful.com/

Contentfulにはデータを保存する大枠としての「Space」があり、その中に個別のデータを保存する領域を作っていきます(下図参照。図中の「textBody」はブログの本文データ、「slug」はブログ記事のURLを指します)。

contentful-structure.png

まずは「blog」という領域を作ります。

ログイン後のContntfulの上部メニューバーで「Content model」をクリックして、右側の「Add conent type」を押し、「Name」と「Api Identifier」には「blog」「Description」には「ブログ」と入力しましょう。

そうすると、この「blog」という領域に保存するデータの項目を決めるよう促されるので、「Add a field」ボタンを押します。

「field」はマークダウンファイルのfrontmatter部分の項目に該当するので、それぞれ作ります。

(「Name」と「Field ID」には、例えばidであれば「id」、titleであれば「title」というように、同じ文字を入力します。)


id→「Number」

date→「Date and time」

title→「Text」

summary→「Text」


ブログの本文にあたるtextBody、そしてslugも作ります。


textBody→「Text」を選択後、「Long text, full-text search」

slug→「Text」


これで下図のようにブログのデータとして保存する各項目ができました。右上にある「Save」ボタンを押して保存します。

done-fields.jpg

ブログデータを保存する領域ができたので、次はデータを入力していきます。

画面上部メニューの「Content」をクリックし、中心部に表示される「Add blog」ボタンを押してください。

マークダウンファイル6つのデータを、それぞれここにコピー&ペーストします。

「slug」欄は次のように入力しましょう。


1つ目の記事: /contentful-first

2つ目の記事: /contentful-second

3つ目の記事: /contentful-third

4つ目の記事: /contentful-fourth

5つ目の記事: /contentful-fifth

6つ目の記事: /contentful-sixth


6つの記事をすべて入力すると次のようになります。

full-blog-ready.jpg

これでContentful側の準備が完了したので、このデータをGatsbyサイトからGraphQLで呼び出して表示させます。

ContentfulとGatsbyをつなげる

Contentfulからデータを取得するためのプラグインをインストールします。

npm install gatsby-source-contentful

これをgatsby-config.jsに追加します。

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

...

  plugins: [
    `gatsby-plugin-sass`,
    `gatsby-transformer-remark`,
    `gatsby-plugin-react-helmet`,
    `gatsby-plugin-image`,
    // ⬇追加
    {
      resolve: `gatsby-source-contentful`,
      options: {
        spaceId: ``,
        accessToken: ``,
      },
    },
    // ⬆追加
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `data`,
        path: `${__dirname}/src/data`,
      },
    },

...

現在は空欄となっているspaceIdaccessTokenを入力していきます。

Contentfulの上部メニューバーの「Settings」から、「API Keys」を選びます。

「Content delivery / preview tokens」タブが開かれていることを確認し、右側の「Add API key」ボタンをクリックします。

「name」は「firstjamstack」と打ち、右側の「Save」ボタンで保存しましょう。

「Space ID」をspaceIdに、「Content Delivery API - access token」をaccessTokenに、それぞれコピーして下のように貼り付けます。

// gatsby-config.js

...

  plugins: [
    `gatsby-plugin-sass`,
    `gatsby-transformer-remark`,
    `gatsby-plugin-react-helmet`,
    `gatsby-plugin-image`,
    {
      resolve: `gatsby-source-contentful`,
      options: {
        spaceId: `xxxxxx6qqsf7`,  // 追加
        accessToken: `xxxxx_4sEXPxxxxxxh51DLxxxxxxoE`,  // 追加
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `data`,
        path: `${__dirname}/src/data`,
      },
    },

...

これでGatsbyとContentfulを接続できたので、gatsby developでGatsbyを起動させ、http://localhost:8000/___graphqlをブラウザで開きます。

すでに起動している場合は再起動してください。

まずはblog.jsに必要なデータを取得しましょう。

一番左の画面でallContentfulBlogedgesnodeと進みます。

blog.jsで必要なのはdatesummaryidtitleslugなので、チェックを入れます。

中央画面のGraphQLクエリをコピーして、現在のものと置き換えます。

ここにはブログ記事の順番を日付順に整えるコード、(sort: {fields: date, order: DESC})も追加してあります。

// blog.js

export default Blog

export const query = graphql`
// ⬇貼り付け
    query BlogQuery {
        allContentfulBlog(sort: {fields: date, order: DESC}) {
            edges {
                node {
                    date
                    summary
                    slug
                    title
                    id
                }
            }
        }
    }
// ⬆貼り付け
`

マークダウンファイルから取得したデータの構造と、Contentfulから取得したデータの構造は異なるので、次のように修正します。

// blog.js

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

export default Blog

...

保存します。

次はsingleBlog.jsの作業です。

gatsby-node.jsを開き、全体を次のように修正します。

// gatsby-node.js

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

exports.createPages = async ({ graphql, actions }) => { 
    const { createPage } = actions 
    const result = await graphql(`              
        query {
            allContentfulBlog {
                edges {
                    node {
                        slug
                    }
                }
            }
        }
    `)
    result.data.allContentfulBlog.edges.forEach(({ node }) => {
        createPage({
            path: `blog${node.slug}`,
            component: path.resolve(`./src/templates/singleBlog.js`),
            context: {
                slug: node.slug,
            },
        })
    })
}  

保存します。

次はsingleBlog.jsからContentfulのデータを取得し、表示させます。

gatsby-node.jsを編集したのでGatsbyを再起動させますが、今の状態ではsingleBlog.jsでエラーが出るので、再起動の前に下のようにコメントアウトしておきましょう。

// singleBlog.js

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
                summary
                id
            }
            html
        }
    }
` */

gatsby developを実行し、http://localhost:8000/___graphqlを開きましょう。

左の画面でcontentfulBlogにチェックを入れ、singleBlog.jsで必要なdatetitle、そして本文にあたるtextBodyを選択します。textBodychildMarkdownRemarkhtmlを選択します。

中央画面のクエリをコピーし、singleBlog.jsファイル下部でコメントアウトしているクエリは消して、貼り付けます。

なお、ここでは変数「slug」も渡す必要があるので、書き加えておきます。

// singleBlog.js

...

export default SingleBlog

export const query = graphql`
    query SingleBlogQuery ($slug: String!){
        contentfulBlog (slug: { eq: $slug }){
            date
            title
            textBody {
                childMarkdownRemark {
                    html
                }
            }
        }
    }
`

そしてblog.jsの時と同じように、コードを修正します。

// single-blog.js

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

export default SingleBlog

...

これでContentfulからデータを取得できるようになりました。

ContentfulとNetlifyをつなげる

自分のGitHub(GitLabなども可)に、ここまで開発したコードをプッシュします。

Netlify(https://www.netlify.com)にGitHubアカウントを使ってログインし、メニュー画面上部「Sites」をクリックして、画面左に「Add new site」ボタンがあるので、そこから「Import an existing project」を選びます。

「GitHub」を選択し、指示に従って、先ほどコードをプッシュしたリポジトリを選びます。

下部の「Deploy site」ボタンを押すと、Netlify上でBuildが行われ、自動でサイトが公開されます。

次はNetlifyとContentfulを、WebHookを使ってつなげます。

WebHookによって、Contentful内のデータの更新が行われるたびにContentfulからNetlifyに「Buildを行ってください」という指示を出すのです。

Netlifyの「Site settings」をクリックし、画面左のメニュー画面から「Build & deploy」を選び、少し下にスクロールすると「Build hooks」というWebHookを設定するところがあります。

「Add build hook」ボタンを押し、「Build hook name」には好きな名前を入力したら「Save」を押します。

生成されたURLをコピーします。

Contentfulへ戻り、メニュー画面上部の「Settings」から「Webhooks」を選びます。

画面右に「Netlify」とあるので、「Add」をクリックします。

「Netlify build hook URL」に先ほどコピーしたURLを貼り付け、「Create webhook」ボタンを押します。

これでNetlifyとContentfulの接続が完了しました。

Contentfulの記事データに変更を加えて「Publish」ボタンを押すと、Netlify上で自動でBuildが始まり、サイトに変更が反映されるのがわかります。

以上でヘッドレスCMSを接続したJamstackサイトが完成です。

本記事では細かい説明や詳しい図解を省略して足早に解説しましたが、拙著「はじめてつくるJamstackサイト with ヘッドレスCMS」では、ビギナーの引っかかりやすいところをわかりやすく解説しているので、参考にしてください。

jamstackbook

【Amazonで見る】