Jamstack は Web 開発のアーキテクチャです。この記事では Jamstack についてご紹介し、Jamstack 構成のシンプルなブログサイトを作ります。

Jamstack とは

Jamstack は Netlify の 創業者 Matt Biilmann 氏が名付けたアーキテクチャです。Jamstack とは一体なんでしょうか。Jamstack の公式サイト jamstack.org では、Jamstack を次のように定義しています。

A modern architecture —

Create fast and secure sites and dynamic apps with JavaScript, APIs, and prerendered Markup, served without web servers.

https://jamstack.org/

和訳すると「JavaScript、API、プリレンダリングされた Markup を使用し、Web サーバなしで提供される高速で安全なサイトおよび動的アプリケーションを作成するモダンアーキテクチャ」となります。

Jamstack は「JavaScript」「API」「プリレンダリングされた Markup」の3つの要素を持ちます。「Jam」の「J」が「JavaScript」、「a」が「API」、そして「m」が「Markup」です。また、Jamstack は Web サーバに依存しないという特徴があります。

JavaScript

すべての動的プログラミングはクライアント(ブラウザ)の JavaScript が行います。クライアントの JavaScript は特定の方法を指しません。フレームワークやライブラリを使ってもよいですし、Vanilla JavaScript でもかまいません。

API

すべてのサーバの処理は Web API を通じて行います。クライアントの JavaScript が Web API を呼び出し、データの送受信を行います。サードパーティのサービスを利用したい場合は Web API を通じて利用します。

プリレンダリングされた Markup

プリレンダリングとは、サイトやアプリケーションの各ページの初期状態を HTML ファイルとして事前に生成することを指します。ページの見た目を定義するテンプレートに実際のデータを注入し、その結果を HTML ファイルとして生成します。プリレンダリングは静的サイトジェネレータやビルドツールを用いてビルド時に行います。

なお、2020年1月時点の公式サイトでは Jamstack の定義から「JavaScript」「API」「Markup」の文字が消えています。しかし、「Jam」の説明のため、この記事では 2019年12月時点の定義をご紹介しました。2020年1月時点の公式サイトの Jamstack の定義は次の通りです。

Fast and secure sites and apps delivered by pre-rendering files and serving them directly from a CDN, removing the requirement to manage or run web servers.

https://jamstack.org/

和訳すると「ファイルをプリレンダリングし CDN から配信される、高速で安全なサイトとアプリケーションで、Webサーバの管理・実行の必要がない」となります。

また、2019年11月時点の公式サイトでは Jamstack の表記が「JAMstack」となっていましたが、2019年12月半ばに「Jamstack」に変更されています。この記事では引き続き、変更後の「Jamstack」と表記します。

ブログサイトの作成

それでは Jamstack 構成のブログサイトを作ってみましょう。

今回は、データソースとして Markdown ファイルを用意します。次に Gatsby を使って Markdown ファイルの内容をデータとして取得し、ブログサイトを作ります。静的サイトジェネレータの Gatsby を使うことで、ビルド時に静的ファイルを出力します。最後に Netlify を使ってブログサイトのビルド、デプロイを行います。

  • データソース:Markdown ファイル
  • 静的サイトジェネレータ:Gatsby
  • ビルド、デプロイ:Netlify

※Gatsby: React ベースの静的サイトジェネレータ
※Netlify: Web サイトの自動化のためのオールインワンのプラットフォーム。ビルド、デプロイ、ホスティングといった機能を提供している

また、サンプルコードは GitHub にアップしています。合わせてご参照ください。
https://github.com/mokajima/jamstack-demo

※後述の Netlify にデプロイするにて、デプロイ時に Git のリポジトリを使用します。ご自身のリポジトリをご用意いただくか、サンプルコードをフォークいただき、お試しください。

環境構築

Gatsby の CLI を使って環境構築を行います。以下のコマンドを実行してください。

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

このコマンドは gatsby-starter-default というスターターコードを使い、jamstack-demo というディレクトリ名で Gatsby のプロジェクトを作成します。

プロジェクトの作成が終わったら cd jamstack-demojamstack-demo ディレクトリに移動し、yarn start で開発サーバを起動します。開発サーバを起動すると http://localhost:8000 でサイトの表示を確認できるようになります。

$ cd jamstack-demo; yarn start

データソースの追加

データソースの Markdown ファイルを追加しましょう。src ディレクトリ直下に markdown-pages ディレクトリを作り、post1.md post2.md post3.md を追加します。

---
title
: "投稿1"
date
: "2020-01-01"
slug
: "post1"
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean et facilisis ligula. Morbi sed blandit elit, ac luctus tellus. Sed at libero condimentum ante vulputate blandit sed eu lectus. Curabitur quis purus fringilla, hendrerit erat ut, sagittis odio. Vivamus et rutrum odio. Proin dignissim eros a odio vestibulum pellentesque. Aenean finibus id velit blandit scelerisque. Duis tempus lorem vitae ligula placerat viverra. Fusce at posuere ligula. Phasellus nec sapien ultricies, ornare magna id, pharetra enim. Ut faucibus elementum orci id facilisis. Mauris pellentesque id lacus a volutpat. Nunc sed est id ipsum efficitur venenatis ac nec lacus.
---
title
: "投稿2"
date
: "2020-01-02"
slug
: "post2"
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean et facilisis ligula. Morbi sed blandit elit, ac luctus tellus. Sed at libero condimentum ante vulputate blandit sed eu lectus. Curabitur quis purus fringilla, hendrerit erat ut, sagittis odio. Vivamus et rutrum odio. Proin dignissim eros a odio vestibulum pellentesque. Aenean finibus id velit blandit scelerisque. Duis tempus lorem vitae ligula placerat viverra. Fusce at posuere ligula. Phasellus nec sapien ultricies, ornare magna id, pharetra enim. Ut faucibus elementum orci id facilisis. Mauris pellentesque id lacus a volutpat. Nunc sed est id ipsum efficitur venenatis ac nec lacus.
---
title
: "投稿3"
date
: "2020-01-03"
slug
: "post3"
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean et facilisis ligula. Morbi sed blandit elit, ac luctus tellus. Sed at libero condimentum ante vulputate blandit sed eu lectus. Curabitur quis purus fringilla, hendrerit erat ut, sagittis odio. Vivamus et rutrum odio. Proin dignissim eros a odio vestibulum pellentesque. Aenean finibus id velit blandit scelerisque. Duis tempus lorem vitae ligula placerat viverra. Fusce at posuere ligula. Phasellus nec sapien ultricies, ornare magna id, pharetra enim. Ut faucibus elementum orci id facilisis. Mauris pellentesque id lacus a volutpat. Nunc sed est id ipsum efficitur venenatis ac nec lacus.

--- のブロック内は frontmatter と呼ばれるデータです。「キー: 値」の形式で記述し、Markdown ファイルにデータを持たせることができます。ここでは title date slug を指定しています。

なお、Gatsby では様々なデータソースを扱うことができ、Markdown ファイルの他にも、API、データベース、CMS、ローカルファイルなどを利用可能です。

ソースプラグインの追加

Gatsby で各種データソースを扱うには「ソースプラグイン」と呼ばれる専用のプラグインを追加する必要があります。ソースプラグインはデータソースからデータを取得するために使用します。Markdown ファイルをデータソースとして扱うには gatsby-source-filesystem が必要です。このプラグインは gatsby-starter-default ではインストール済みですが、設定の追加が必要です。そこで gatsby-config.js を編集します。gatsby-config.js はその名の通り、Gatsby の設定に関する記述を行うためのファイルです。

gatsby-config.jsplugins 配列の中に resolve: gatsby-source-filesystem の箇所があります。その下の options を書き換えてください。

  plugins: [
   
`gatsby-plugin-react-helmet`,
   
{
      resolve
: `gatsby-source-filesystem`,
      options
: {
        name
: `markdown`,
        path
: `${__dirname}/src/markdown-pages`,
     
},
   
},

resolve には使用するプラグイン名を指定します。使用するプラグインによってはオプションを持たせることができ、options を使って指定します。ここでは name に識別用の名前、path にデータソースの場所を指定しています。

gatsby-config.js を書き換えたら開発サーバを再起動してください。

開発サーバを再起動したら、Markdown ファイルをデータとして取得できているか確認してみましょう。Gatsby では GraphQL を使ってデータにアクセスします。

GraphQL

GraphQL は Facebook が開発した API のためのクエリ言語です。GraphQL のクエリは query クエリ名 {} の形で定義します。query キーワードとクエリ名は省略可能です。

以下のクエリは、データソースの追加で追加した Markdown ファイルをデータとして取得できているかを確認するためのクエリです。ファイルの相対パスの relativePath とファイルの識別名(データソースの追加gatsby-config.jsgatsby-source-filesystem の設定で指定した name)の2つのフィールドを取得しています。

query MyQuery {
  allFile {
    edges {
      node {
        relativePath,
        sourceInstanceName
      }
    }
  }
}

query は「データを取得する」という操作を表します。query では取得したいデータの構造を定義すると、その通りのレスポンスを得ることができます。例えば、上記のクエリを実行した結果は次のようになります。

※GraphQL では query の他にも mutationssubscription という操作が存在します。

{
  "data": {
    "allFile": {
      "edges": [
        {
          "node": {
            "relativePath": "post1.md",
            "sourceInstanceName": "markdown"
          }
        },
        {
          "node": {
            "relativePath": "post2.md",
            "sourceInstanceName": "markdown"
          }
        },
        {
          "node": {
            "relativePath": "post3.md",
            "sourceInstanceName": "markdown"
          }
        }
      ]
    }
  }
}

allFile edges node relativePath sourceInstanceName はデータのフィールドです。allFile はソースプラグイン gatsby-source-filesystem によって利用可能となったフィールドで、すべてのファイルを取得します。使用するソースプラグインによって、利用可能なフィールドが変わります。

GraphiQL

GraphiQL は GraphQL の IDE(統合開発環境)です。GraphQL のクエリを実行したり、各フィールドの構造を確認したりすることができます。開発サーバの起動中は http://localhost:8000/___graphql で利用可能です。

以下のクエリを GraphiQL 上で実行してみましょう。GraphiQL を開くと左側にエクスプローラがあり、その右隣にはクエリを入力するエディタがあります。エディタに以下のクエリを入力し、再生ボタンをクリック、ないしは Ctrl-Enter を押すとクエリが実行されます。クエリ結果はエディタの右隣に表示されます。

query MyQuery {
  allFile {
    edges {
      node {
        relativePath,
        sourceInstanceName
      }
    }
  }
}

クエリの実行結果から、データソースの追加で追加した Markdown ファイルをデータとして取得できていることがわかります。

なお、GraphiQL のエクスプローラでは各フィールドの構造を確認したり、クエリを組み立てたりすることが可能です。例えば、allFile の左横の三角形を開いていくと、edgesnode が、noderelativePathsourceInstanceName が含まれているのがわかります。また、各フィールド名の左横のチェックボックスにチェックを入れると、そのフィールドがエディタのクエリに追加されます。

トランスフォーマープラグインの追加

ソースプラグインの追加で Markdown ファイルをデータとして取得できるようになりました。次は Markdown ファイルをパースし、HTML データとして利用可能にする必要があります。そこで、トランスフォーマープラグインの gatsby-transformer-remark を追加します。トランスフォーマープラグインは「トランスフォーマー(transformer)」とあるように、あるデータを別のデータに変換するためのプラグインです。gatsby-transformer-remark は Markdown ファイルをパースし、HTML データに変換します。

$ yarn add gatsby-transformer-remark

gatsby-transformer-remark をインストールしたら、gatsby-config.jsgatsby-transformer-remark を追記します。

  plugins: [
   
`gatsby-plugin-react-helmet`,
   
{
      resolve
: `gatsby-source-filesystem`,
      options
: {
        name
: `markdown`,
        path
: `${__dirname}/src/markdown-pages`,
     
},
   
},
   
`gatsby-transformer-remark`,

追記したら、開発サーバを再起動してください。

gatsby-transformer-remark によって、allMarkdownRemarkMarkdownRemark といったフィールドがクエリで利用可能になります。GraphiQL で以下のクエリを実行すると、Markdown ファイル内で指定した title slug date に加えて、本文の HTML データを取得することができます。

query MyQuery {
  allMarkdownRemark {
    edges {
      node {
        frontmatter {
          title
          date
          slug
        }
        html
      }
    }
  }
}

このデータを使ってページの作成を行っていきましょう。

一覧ページの作成

Gatsby では、src/pages に追加したコンポーネントファイルがそのままルーティングとなります。そのため、src/pages/hoge.js があれば、/hoge というページが生成されます。

今回はブログサイトを作るため、トップページを一覧ページとしましょう。src/pages/index.js がトップページに該当します。index.js を以下のように変更してください。

import React from "react"
import { graphql, Link } from "gatsby"

import Layout from "../components/layout"

const IndexPage = ({ data }) => (
 
<Layout>
   
<h1>Hello, Gatsby!</h1>
   
{data.allMarkdownRemark.edges.map(({ node }) => (
     
<article key={node.frontmatter.slug}>
       
<h2>
         
<Link to={`/${node.frontmatter.slug}`}>
           
{node.frontmatter.title}
         
</Link>
        </
h2>
       
<time dateTime={node.frontmatter.date}>{node.frontmatter.date}</time>
       
<p>{node.excerpt}</p>
     
</article>
    ))}
  </
Layout>
)

export const query = graphql`
  query {
    allMarkdownRemark {
      edges {
        node {
          frontmatter {
            title
            date(formatString: "YYYY年MM月DD日")
            slug
          }
          excerpt
        }
      }
    }
  }
`


export default IndexPage

export const query = graphql ではページのクエリを定義しています。allMarkdownRemark はすべての Markdown ファイルを取得するためのフィールドです。ここでは allMarkdownRemark の構造に従い、4つのフィールド title date slug excerpt の値を取得するクエリを定義しています(allMarkdownRemark の構造は GraphiQL のエクスプローラで確認可能です)。

また、フィールドによっては引数を渡すことができます。ここでは dateformatString を引数として渡し、日付のフォーマット方法を指定しています。

そして、export const 任意の変数名 = graphql`` の形でクエリを囲うことで、Gatsby がページのクエリとして認識し、その実行結果をそのページのコンポーネント(ここでは IndexPage)に渡します。

IndexPage コンポーネントでは data を props として受け取り、それを元に一覧ページを作成しています。

これで一覧ページが作成できました。

一覧ページの並び替え

一覧ページを見てみると、並び順が日付順になっていないようです。一覧のソートを行うため、date と同じように、allMarkdownRemark に引数を指定します。

export const query = graphql`
  query {
    allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {

allMarkdownRemark では sort を引数として指定可能です。引数の情報は GraphiQL で確認することができます。GraphiQL のエディタ上のフィールド名を Ctrl を押した状態でクリックします。すると、クエリの実行結果の右隣に allMarkdownRemark の情報が表示され、ARGUMENTS(引数) に filter limit skip sort があるのがわかります。

ARGUMENTS の sort: MarkdownRemarkSortInputMarkdownRemarkSortInputsort の型を表しています。MarkdownRemarkSortInput をクリックすると、MarkdownRemarkSortInput の情報が表示され、fieldsorder を持つことがわかります。

また、fields: [MarkdownRemarkFieldsEnum]MarkdownRemarkFieldsEnum をクリックすると、fields に指定可能な値がわかります。このサンプルコードでは id frontmatter___title frontmatter___date frontmatter___slug を指定可能です。

ここでは fields[frontmatter___date] を、orderDESC を指定し、date の降順で表示するようにしました。

詳細ページの作成

次は詳細ページを作っていきましょう。一覧ページの作成では、トップページを一覧ページとするため、src/pages/index.js を編集しました。詳細ページの場合は記事によって URL が変わるため、src/pages/ 内の単一ファイルでページを作成することができません。詳細ページを動的に生成するためには、ページの雛形となるテンプレートを作成し、gatsby-node.js にページの生成ロジックを記述します。

テンプレートの作成

まず、データを注入する元となるテンプレートを作成します。src 上で templates ディレクトリを作成し、post.js を追加します。

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

import Layout from "../components/layout"

export default ({ data }) => {
 
const post = data.markdownRemark
 
return (
   
<Layout>
     
<div>
       
<h1>{post.frontmatter.title}</h1>
       
<div dangerouslySetInnerHTML={{ __html: post.html }} />
     
</div>
    </
Layout>
 
)
}

export const query = graphql`
  query($slug: String!) {
    markdownRemark(frontmatter: { slug: { eq: $slug } }) {
      html
      frontmatter {
        title
      }
    }
  }
`

src/pages/index.js では allMarkdownRemark を使いましたが、今回は markdownRemark を使っています。allMarkdownRemark がすべての Markdown ファイルを取得するためのフィールドであるのに対し、markdownRemark は単独の Markdown ファイルを取得するためのフィールドです。

src/pages/index.jsallMarkdownRemark では sort を引数として渡しましたが、markdownRemark では frontmatter を引数として渡しています。frontmatter では取得する記事の条件を指定でき、ここでは frontmatterslug$slug と等しい記事を指定しています。そして、2つのフィールド htmltitle の値を取得しています。

markdownRemark の引数の情報は一覧ページの並び替えと同様に GraphiQL で確認可能です。
$slugquery($slug: String!) については後述します。

ページの生成ロジックを作成

ページの動的生成を行うには Gatsby Node API を使用する必要があります。Gatsby Node API が利用可能となるのが gatsby-node.js というファイル内です。そこで、gatsby-node.js にページの生成ロジックを記述します。

gatsby-node.js を以下のように書き換えてください。

const path = require(`path`)

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

 
const result = await graphql(`
    query {
      allMarkdownRemark {
        edges {
          node {
            frontmatter {
              slug
            }
          }
        }
      }
    }
  `
)

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

gatsby-node.js では exports.API名 = gatsbyNodeHelpers => {} の形で API を使用します。API は第一引数に gatsbyNodeHelpers を受け取ります。gatsbyNodeHelpers はヘルパーオブジェクトで、Gatsby Node API で共通です(一部 API 特有のフィールドもあります)。ここでは gatsbyNodeHelpersgraphqlactions を使用しています。

actions は関数の集まりで、その中の createPage を用いてページを動的に生成することができます。なお、Gatsby では状態管理のために内部で Redux を使っており、actions の持つ各関数は Redux の bindActionCreators でバインドされた action creators と同等です。

const result = await graphql( では allMarkdownRemark を使い、各記事の slug を取得するクエリを定義しています。取得結果をループで回しているのが result.data.allMarkdownRemark.edges.forEach(({ node }) => { の箇所です。ループごとに createPage を呼び出し、ページを生成します。

createPage には path component context を持つオブジェクトを渡しています。path はその記事のパス、componentテンプレートの作成で作成したテンプレートを指定しています。context に渡すデータはページクエリ内で GraphQL の変数として使用することができます。ここでは slug を渡しており、それが src/templates/post.jsquery($slug: String!) { の部分です。String! の部分は変数 $slug の型が文字列型で null を許容しないことを意味します。

gatsby-node.js を書き換えたら開発サーバを再起動してください。/post1/post2 にアクセスすると、詳細ページが表示されます。

ブログサイトをプリレンダリングする

一通りブログサイトの実装が終わりました。Jamstack 構成ではプリレンダリングした静的ファイルをデプロイするため、試しに静的ファイルを出力してみましょう。yarn build を行うと、public ディレクトリに静的ファイルが出力されます。

$ yarn build

public
├── 404
├── 404.html
├── app-e2eabd7e5c5c86c7a2d2.js
├── app-e2eabd7e5c5c86c7a2d2.js.map
├── chunk-map.json
├── commons-e97206a1ccd52f6b5032.js
├── commons-e97206a1ccd52f6b5032.js.map
├── component---src-pages-404-js-9b37774f8033162af493.js
├── component---src-pages-404-js-9b37774f8033162af493.js.map
├── component---src-pages-index-js-b5379c7ba1f83fc5637f.js
├── component---src-pages-index-js-b5379c7ba1f83fc5637f.js.map
├── component---src-pages-index-js-cac91da790e6c553af7b.js
├── component---src-pages-index-js-cac91da790e6c553af7b.js.map
├── component---src-pages-page-2-js-94a302a1a52c4902c9da.js
├── component---src-pages-page-2-js-94a302a1a52c4902c9da.js.map
├── component---src-templates-post-js-cb0a2eaf4915644b1921.js
├── component---src-templates-post-js-cb0a2eaf4915644b1921.js.map
├── icons
├── index.html
├── manifest.webmanifest
├── page-2
├── page-data
├── post1
├── post2
├── post3
├── static
├── styles-14d6261e5b1ae6dcfccc.js
├── styles-14d6261e5b1ae6dcfccc.js.map
├── styles.d5ef0abb16ee382cea7f.css
├── webpack-runtime-332e9a5d5c23d0061959.js
├── webpack-runtime-332e9a5d5c23d0061959.js.map
├── webpack-runtime-4adcc68246c06f1ce32d.js
├── webpack-runtime-4adcc68246c06f1ce32d.js.map
└── webpack.stats.json

yarn serve を実行すると、出力した静的ファイルをローカルで実行することができます。

$ yarn serve

Netlify にデプロイする

作成したブログサイトを Netlify にデプロイします。Netlify のアカウントを作成するか、ログインを行ってください。Netlify のデプロイはサインアップ・ログイン後の画面の「New site from Git」から行います。

「New site from Git」をクリックしたら、リポジトリの選択を行います。リポジトリがホスティングされているサービスを「GitHub」「GitLab」「Bitbucket」のいずれかから選択します。サンプルコードは GitHub のため、GitHub を選択します。

リポジトリの一覧が表示されるため、デプロイを行うリポジトリを選択してください。

リポジトリを選択したら、デプロイの設定を行います。「Branch to deploy」ではデプロイしたいブランチ名を選択します。master 以外を指定することも可能です。「Basic build settings」は初期設定のままで問題ありません。設定を確認したら、「Deploy site」をクリックします。

「Deploy site」をクリックすると、デプロイしたサイトの詳細ページに遷移します。黄色の文字で「Site deploy in progress」と表示されている場合はデプロイ中です。デプロイが終わると緑色の文字でサイトの URL が表示されます。サイトの URL は自動的に割り当てられます。

サイトの詳細ページでは「Site settings」の「Change site name」からサイトの URL を変更したり、「Domain settings」からカスタムドメインの設定を行ったりなど、様々な操作、設定を行うことが可能です。ここではサイトの URL を mokajima-jamstack-demo.netlify.com に変更してみました。

また、デプロイの設定の「Branch to deploy」ではデプロイしたいブランチ名を選択しましたが、このブランチに新しいコミットをプッシュすると、Netlify がそれを検知して自動でビルド、デプロイを行ってくれます。デプロイの履歴は管理され、クリックひとつで前のデプロイの状態に戻す、といったことも可能です。

なお、ブログサイトをプリレンダリングするで出力した public ディレクトリを「Want to deploy a new site without connecting to Git?」と書かれたエリアにドラッグ&ドロップし、デプロイすることも可能ですが、この場合は Netlify の自動ビルド、デプロイが行われません。

これでデプロイは完了です。

最後に

今回はデータソースとして Markdown ファイルを扱いましたが、Gatsby では WordPress の REST API や Contentful、microCMS のような Headless CMS など、様々なデータソースを扱うことができます。また、Jamstack は Gatsby に限定されないため、要件に応じて任意の静的サイトジェネレータを使うことが可能です。

この記事ではご紹介しませんでしたが、外部 API を利用することで Jamstack 構成のサイトに検索機能や決済機能などの機能を盛り込む、といったことも可能です。

フロントエンドの技術で行える Jamstack 構成の開発をぜひ試してみてください。

参考

Jamstack

Gatsby

Netlify