Avatar
writer 阿部 文人

necco note neccoのノートとお知らせ。

Shifter Headless と Gridsome とShopifyでヘッドレスメディアコマースサイトを1日でつくる

「表題の構成で自社サイトをずっと公開する」と言って全く進まず、まさかの2020年を終えようとしています。
年末にじっくり進めるべくShifter Advent Calendar 17日目を担当し、気合いをいれたいと思います。


このブログの手順どおり進められれば、 WordPress を Headless CMSとして利用可能な「Shifter Headless」とeコマースサービスである「Shopify」を使ってヘッドレスメディアコマースサイトを公開まで持っていけるはずです!
ヘッドレスメディアコマースは勝手に造語してしまってるのですが、ECサイトとブログが合わさったサイトだと思ってもらえれば幸いです。

はじめに、今回完成したデモサイトはこちらです。

https://shifter-headless-gridsome-shopify.vercel.app/

システムアーキテクチャ図はこんな感じです。

ではやってみましょう〜!

下記の流れで実際に公開するところまで進めてみます。

  1. Shifter Headlessでアカウントを作成して記事を取得するためのURLを準備する
  2. Shifter Headlessで表示する記事、固定ページを作成する
  3. Shopifyアカウントを作成して商品情報を入れる
  4. Shopifyで商品情報を取得するためにアクセストークンの準備する
  5. GridsomeのShopifyスターターで商品情報を取得する
  6. GridsomeのWordPress スターターでブログ記事を表示する
  7. 2つのスターターを組み合わせてメディアコマースサイトにする
  8. Vercelにデプロイする

ここまでをやってみたいと思います!

1.Shifter Headlessでアカウントを作成して記事を取得するためのURLを準備する

https://www.getshifter.io/ja/

Shifterのサイトからサインアップして管理画面に入ります。

Shifter Headlessを選択してCreate New HedlessのボタンからHeadless に最適化されたWordPressを立ち上げましょう。
1分ほどで立ち上がりますので少しだけ待ちましょう〜。

サイトが立ち上がったら、Overviewに記載があるWordPress URLを保存しておきましょう。このURLはフロントエンドを作成していくときに必要になります。

2.Shifter Headlessで表示する記事、固定ページを作成する

WordPress admin URLからWordPressの管理画面に入り、投稿1記事と固定ページ1ページを作成します。

投稿にはアイキャッチを設定しておかないとフロントエンド側で記事取得時にエラーを起こしてしまうので、設定しておきましょう。

固定ページもフロント側で取得しますので、aboutにスラッグを変えてアイキャッチや記事をいれておきます。

次にフロントエンドのGridsomeで情報取得できるように、WP GraphQLのプラグインを有効化しておきます。

3.Shopifyアカウントを作成して商品情報を入れる

Shopifyのアカウント作成方法は割愛しますのでがんばってつくりましょう。

Shopifyのアカウントができたら、右上の「商品を追加する」ボタンから商品を追加してみましょう。

商品の詳細ページではタイトル(商品名)や説明文、写真やバリエーションも追加できます。今回のテストでは3商品追加してみました。

4.Shopifyで商品情報を取得するためにアクセストークンの準備する

Shopifyに登録した商品情報をフロントエンド側で取り出すために、ストアフロントのアクセストークンを準備します。
これがないとフロントエンドのGridsome側から商品情報が取得できないためです。

まずはShopifyの管理画面でアプリ管理をクリックします。

次にプライベートアプリを管理するをクリック

次に右上の「新しいプライベートアプリを作成する」をクリックし、プライベートアプリを作成します。

次にアプリ名やメールアドレスを入力します。

同じページの下部にいくと

「このアプリがストアフロントAPIを使用してストアフロントデータにアクセスできるようにする」にチェックをいれ、さらに下部のストアフロントのアクセストークンをコピーしておきます。こちらは次の手順で利用します。

5.GridsomeのShopifyスターターで商品情報を取得する

ではここから、フロントエンドで商品を取得するテーマを作成していきたいと思います。
今回はGridsomeを利用してShopifyから商品情報を取得してみます。


Gridsome = JavaScriptフレームワークであるVue.jsのプログレッシブフレームワークでGraphQLを利用して情報を取得するという特徴のあるフレームワークです。同じくJavaScriptフレームワークReactにはGatsbyがあります。GridsomeはGatsbyから影響を受けてつくられたものです。

ありがたいことにGridsomeを利用してShopifyのサイトがすぐ実装できるようにスターターテーマが用意されていますので、今回はこちらの右側のShopify Starterを利用させてもらい商品情報を取得していきます。

https://gridsome.org/starters/gridsome-shopify-starter/

Gridsome Shopify Starter のページにいくとローカル環境での手順が記述されてますので、順番にいれていきます。

yarn global add @gridsome/cli

GridsomeのCLIをいれて

git clone https://github.com/jsappme/gridsome-shopify-starter.git

テーマをクローンしてきます。

yarn install

クローンしたディレクトリに移動してモジュールをいれましょう。

次にGridsome Shopify Starterのgridsome.config.jsを修正していきます。

    {
      use: 'gridsome-source-shopify',
      options: {
        storeName: process.env.GRIDSOME_SHOPIFY_STOREFRONT,
        storefrontToken: process.env.GRIDSOME_SHOPIFY_STOREFRONT_TOKEN
      }
    },

ストア名と手順4でコピーしておいたShopifyのストアフロントアクセストークンをいれます。

storeNameには ShopifyのURL(xxxxxxx.myshopify.comのxxxxxxxの部分)をシングルクオテーションで囲っていれます。

トークンのほうもシングルクオテーションで囲っていれましょう。こんな感じになります。

{
  use: 'gridsome-source-shopify',
  options: {
    storeName: 'xxxxxxxxx',
    storefrontToken: '45c51csamplebnecco4444yourtoken'
  }
},

このように入力します。

次に、これは私の場合だけなのかもしれませんが、商品詳細ページがうまく表示できなかったので、同じgridsome.config.jsで下記の部分をhandleからidに修正しました。

templates: {
    ShopifyProduct: [
      {
        path: '/product/:handle',
        component: './src/templates/Product.vue'
      }
    ],
    ShopifyCollection: [
      {
        path: '/collection/:handle',
        component: './src/templates/Collection.vue'
      }
    ]
  },

この:handleの部分をそれぞれ:idに変更します。

templates: {
    ShopifyProduct: [
      {
        path: '/product/:id',
        component: './src/templates/Product.vue'
      }
    ],
    ShopifyCollection: [
      {
        path: '/collection/:id',
        component: './src/templates/Collection.vue'
      }
    ]
  },

こんな感じです。

次に、トップページの商品一覧から詳細ページへいくリンク情報の取得部分も修正します。(これは私の商品情報の入れ方が悪い可能性があるので、編集は不要かもしれません。上記と関連した修正です。)

gridsome-shopify-starter/src/pages/Index.vue

<script>
export default {
  metaInfo: {
    title: 'Home'
  },
  computed: {
    //featuredProducts () { return this.$page.allShopifyProduct.edges }
    featuredProducts () { 
      return this.$page.allShopifyProduct.edges.filter(prod => prod.node.availableForSale === true)
    }
  },
  methods: {
		selectProduct(product) {
      this.$router.push({ path: "/product/" + product.node.id })
    },
  }
}
</script>

の methods:内の product.node.handle を product.node.id に書き換えます。上記は書き換え済みのコードです。

これができたらターミナルで

gridsome develop

してみましょう。ローカルで商品情報を取得したサイトが立ち上がるはずです。

トップページ

ブラウザが自動で立ち上がらなかったら http://localhost:8080/ をいれてアクセスしてみましょう。

商品詳細ページ
カートページ

あっというまにShopifyから商品情報を取得したヘッドレスコマースサイトが立ち上がりました!!
ちなみにこのテーマ下記の特徴を最初から持っていてすごい。。。

  • 4 x 100% Highest Scores on Google Lighthouse!
  • Full SSR (Server Side Redenring) Static Site
  • Progressive Web App (PWA) with “Add to Home Screen” button.
  • Connected to Shopify backend
  • Home Page showing Featured Products
  • Product Search
  • Product Page
  • Collection Search
  • All Collections Page
  • TailwindCSS with PurgeCSS to remove unused css.
  • SEO optimized
  • Sitemap

すばらしい〜!

6.GridsomeのWordPress スターターでブログ記事を表示する

さて、商品情報を取得したところでやめておけばいいものを、そこはメディアコマースですからね。
ブログ記事を投稿できるよう手順5のShopifyテーマに、お待ちかねShifter Headlessで作成した記事や固定ページを表示できるようにしていきます。

まずはブログの投稿と固定ページの情報取得をためしてみます。

こちらもShifter Headlessに最適化されたGridsomeテーマShifter Partner Network株式会社HAMWORKSさんが公開してくれているので、まずはこちらを利用させていただいてShifter Headlessの情報を取得してみます。

Gridsome Shifter

git clone https://github.com/torounit/gridsome-shifter.git

クローンしてきましょう。

クローンしたフォルダに移動して

yarn install

してモジュールをいれます。

次に gridsome.config.js に Shifterアカウントで作成した WordPress URLを記述します。

plugins: [
    {
      use: '@gridsome/source-wordpress',
      options: {
        baseUrl: process.env.CONTAINER_URL,
        typeName: 'WordPress', // GraphQL schema name (Optional)
      }
    }
  ]

の baseUrlに記述します。

plugins: [
    {
      use: '@gridsome/source-wordpress',
      options: {
        baseUrl: plugins: [
    {
      use: '@gridsome/source-wordpress',
      options: {
        baseUrl: 'https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.hl-a.getshifter.co',
        typeName: 'WordPress', // GraphQL schema name (Optional)
      }
    }
  ],
        typeName: 'WordPress', // GraphQL schema name (Optional)
      }
    }
  ]

こんな感じです。

このあとShopifyテーマのほうでこのWordPressのページなどを追加していくのでGridsomeのバージョンはShopifyのテーマ側のバージョンとあわせておきます。

package.json の gridsomeのバージョンを0.7.19にして yarn installしなおしておきます。

"dependencies": {
    "@gridsome/source-wordpress": "^0.4.0",
    "gridsome": "^0.7.19"
}

ここまできたら

gridsome develop

して、ローカル環境でShifter Headlessにいれた記事や固定ページを取得できるか確認します。

体裁は整っていませんが、無事にShifter Headlessにいれたブログ記事と上部メニューのAboutをクリックして固定ページの情報を取得することができました。

7.2つのスターターを組み合わせてメディアコマースサイトにする

次に手順5と6のGridsomeテーマをあわせて1つのサイトにしていきます。

今回はShopifyテーマをベースにしますので、WordPressテーマのファイルなどをShopifyテーマに移植していきます。
まずはShifter Headlessで作成した固定ページ(ここではAboutページ)の情報を取得して表示します。

次にWordPress スターターのgridsome.config.jsに記述された template:の中身とplugin:の中身をShopifyのgridsome.config.jsにコピーします。ハイライトの部分が追記箇所になります。

module.exports = {
  siteName: 'Gridsome Shopify Starter',
  siteUrl: 'https://gridsome-shopify.netlify.app/',
  siteDescription: 'PWA Headless ecommerce Gridsome Shopify starter',
  titleTemplate: '%s - Gridsome + Shopify',
  icon: {
    favicon: "./src/favicon.png",
    touchicon: "./src/favicon.png"
  },
  plugins: [
    'gridsome-plugin-robots',
    'gridsome-plugin-tailwindcss',
    {
      use: '@gridsome/source-wordpress',
      options: {
        baseUrl: 'https://xxxxxxxxxxxxxxxxxxxxxxxxx.hl-a.getshifter.co',
        typeName: 'WordPress', // GraphQL schema name (Optional)
      }
    },
    {
      use: 'gridsome-plugin-pwa',
      options: {
          title: 'Gridsome Shopify Starter',
          description: 'PWA Headless ecommerce Gridsome Shopify starter',// Optional
          startUrl: '/',
          display: 'standalone',
          gcm_sender_id: undefined,
          statusBarStyle: 'default',
          manifestPath: 'manifest.json',
          disableServiceWorker: true,
          serviceWorkerPath: 'service-worker.js',
          cachedFileTypes: 'js,json,css,html,png,jpg,jpeg,svg',
          shortName: 'Gridsome Shopify',
          themeColor: '#000000',
          lang: "en-US",
          backgroundColor: '#000000',
          icon: './src/favicon.png', // must be provided like 'src/favicon.png'
          msTileImage: 'Gridsome Shopify',
          msTileColor: '#000000'
      }
    },
    {
      use: '@gridsome/plugin-sitemap',
      options: {
        exclude: ['/exclude-me'],
        config: {
          '/collections/*': {
            changefreq: 'daily',
            priority: 0.5
          },
          '/product/*': {
            changefreq: 'daily',
            priority: 0.5
          }
        }
      }
    },
    {
      use: 'gridsome-source-shopify',
      options: {
        storeName: 'xxxxxxxx',
        storefrontToken: 'xxxxxxxxxxxxx'
      }
    },
    {
      use: '@gridsome/plugin-google-analytics',
      options: {
        id: ''
      }
    },
    {
      use: 'gridsome-plugin-flexsearch',
      options: {
        flexsearch: {
          profile: 'match'
        },
        collections: [
          {
            typeName: 'ShopifyProduct',
            indexName: 'Product',
            fields: ['title', 'handle', 'description']
          },
          {
            typeName: 'ShopifyCollection',
            indexName: 'Collection',
            fields: ['title', 'handle', 'description']
          }
        ],
        searchFields: ['title', 'handle', 'tags']
      }
    }
  ],
  templates: {
    ShopifyProduct: [
      {
        path: '/product/:id',
        component: './src/templates/Product.vue'
      }
    ],
    ShopifyCollection: [
      {
        path: '/collection/:id',
        component: './src/templates/Collection.vue'
      }
    ],
    WordPressCategory: '/category/:slug', // adds route for "category" post type (Optional)
    WordPressPost: '/:year/:month/:day/:id', //adds route for "post" post type (Optional)
    WordPressPostTag: '/tag/:slug', // adds route for "post_tag" post type (Optional)
    WordPressPage: [
      {
        path: (node) => {
          const url = new URL(node.link);
          return `${url.pathname}`
        }
      }
    ]
  },
  transformers: {
  },
}

次にShopifyテーマのpackage.jsonに下記ハイライト部分を追記して、 yarn install してモジュールを追加しましょう。

"devDependencies": {
  "@gridsome/source-wordpress": "^0.4.0",
  "gridsome-plugin-tailwindcss": "^2.2.48"
}

次にWordPressスターターにあるファイルを移植していきます。

gridsome-shifter/src/templates
の中の
WordPressPostTag.vue
WordPressPost.vue
WordPressPage.vue
WordPressCategory.vue
4ファイルと
gridsome-shifter/src/components/Post.vue
のファイルを同じディレクトリにいれましょう。

それぞれShopifyテーマのほうをこのようなファイル構造にします。

次にShopifyテーマの上部メニューのリンクを修正します。
gridsome-shopify-starter/src/layouts/Default.vue のAboutリンク部分を下記に置き換えましょう。

<li>
  <g-link class="nav__link" to="/about/">About</g-link>
</li>

できたらターミナルで

gridsome develop

して、ローカルでサイトを立ち上げます。

上部のメニューのAboutをクリックすると、Shifter Headlessで入力したコンテンツが出力されるはずです。フロントエンド側では取得するべき情報、例えばアイキャッチやタグ、カテゴリーが設定しないとエラーになりますのでShifter Headless側でいれておきましょう。

メニューのAboutをクリックすると固定ページの情報を取得して表示できる

<template>
  <Layout>
    <div class="container-inner mx-auto py-16">
      <h1 class="text-4xl font-bold text-center" v-html="$page.wordPressPage.title"/>
      <img
        v-if="$page.wordPressPage.featuredMedia"
        :src="$page.wordPressPage.featuredMedia.sourceUrl"
        :width="$page.wordPressPage.featuredMedia.mediaDetails.width"
        :alt="$page.wordPressPage.featuredMedia.altText"
      />
    </div>
    <div class="container-inner mx-auto">
      <div v-html="$page.wordPressPage.content"/>
    </div>
  </Layout>
</template>

Gridsome Shopify Starter では Tailwind が動きますのでちょっとだけ上記のように /gridsome-shopify-starter/src/templates/WordPressPage.vue を整えました。

次にブログ一覧とブログ詳細ページの表示をつくっていきます。

/gridsome-shifter/src/pages/Index.vue の中身をコピーして

/gridsome-shopify-starter/src/pages の中に Blog.vue という新規ファイルを作成して貼りつけましょう!

次に、先程Shopifyスターターで追加したAboutリンクの下に上記のBlog.vueへのリンクを追加します。

/gridsome-shopify-starter/src/layouts/Default.vue

<li>
  <g-link class="nav__link" to="/about/">About</g-link>
</li>
<li>
  <g-link class="nav__link" to="/blog/">Blog</g-link>
</li>

メニューにBlogページを追加できたら、すでにShifter Headlessに投稿した記事の一覧が出力されると思います。

Tailwindを使って下記のように一覧ページを整えてみました。

/gridsome-shopify-starter/src/pages/Blog.vue

<template>
  <Layout>
    <div class="relative bg-gray-50 pt-16 pb-20 px-4 sm:px-6 lg:pt-24 lg:pb-28 lg:px-8">
      <div class="absolute inset-0">
        <div class="bg-white h-1/3 sm:h-2/3"></div>
      </div>
      <div class="relative max-w-7xl mx-auto">
        <div class="text-center">
          <h2 class="text-3xl tracking-tight font-extrabold text-gray-900 sm:text-4xl">
            blog
          </h2>
          <p class="mt-3 max-w-2xl mx-auto text-xl text-gray-500 sm:mt-4">
            ブログ一覧ページです。
          </p>
        </div>
        <div class="mt-12 max-w-lg mx-auto grid gap-5 lg:grid-cols-3 lg:max-w-none" >
          <div
            class="flex flex-col rounded-lg shadow-lg overflow-hidden"
            v-for="{ node } in $page.allWordPressPost.edges" :key="node.id"
          >
            <Post :post="node" />
          </div>
        </div>
      </div>
    </div>
  </Layout>
</template>

<page-query>
query Home ($page: Int) {
  allWordPressPost (page: $page, perPage: 10) @paginate {
    pageInfo {
      totalPages
      currentPage
    }
    edges {
      node {
        id
        title
        path
        excerpt
        featuredMedia {
          sourceUrl
          altText
          mediaDetails {
            width
          }
        }
      }
    }
  }
}
</page-query>

<script>
import { Pager } from 'gridsome'
import Post from '~/components/Post.vue'

export default {
  components: {
    Pager,
    Post
  },
  metaInfo: {
    title: 'Welcome to my blog :)'
  }
}
</script>

これでブログ一覧とブログ詳細ページが完成したので、次はVercelにデプロイして公開してみましょう!

8.作成したGridsomeのテーマをVercelにデプロイする

GitLabやGitHibに、今回作成したファイル一式をPushしましょう。

リポジトリにPushができたら、Vercelからそのリポジトリをインポートします。このような画面になるのでリポジトリのURLをいれてContinueボタンを押します。

Gridsomeを選択します。Buildコマンドは特に設定しなくて大丈夫です。

右下のDeployボタンを押すとVercelがビルドを始めます。

無事にビルドができればこのよう画面になりますので、Visitボタンをおしてみましょう!

https://shifter-headless-gridsome-shopify.vercel.app/

こちらがVercelで公開した今回のサイトになります。
どうでしたでしょうか?みなさんの商品やブログ、ページは表示されましたか?

まとめ

「Shifter Headless と Gridsome とShopifyでヘッドレスメディアコマースサイトを1日でつくる」ということで、たったの1日で表示も高速で快適なメディアコマースサイトの立ち上げを行いました。

CSSフレームワークであるTailwindもうまく使えばClassを付与するだけで見栄えも素早く整えていけますので、ぜひ試してみてください。

ブログの更新はWordPressに任せて、商品管理はShopifyに。
今回フロントエンドの表示はVueなどで自由に書けるGridsomeにしてみましたが、Nuxt.jsやNext.jsでも同じようなシステム構成で実装可能だと思います。GraphQLだと情報取得のクエリが簡単に書けるので、GridsomeやGatsby.jsを試してみるのもいいかなと思います。

みなさんも、年末はぜひECサイトとブログが合わさってかつJamstackなヘッドレスメディアコマースサイトの制作に挑戦してみてください。
とにかく表示が早くて快適です!

Avatar
阿部 文人 Fumito Abe

CEO / クリエイティブディレクター / デザインエンジニア 東京都生まれ。 オフィス仲介、外国人専用ゲストハウスなどの不動産業界にて自社ブランデング・ウェブマーケティングに従事。ウェブサイトの解析、広告運用、多言語サイトの制作を経験。その後、サンフランシスコにて語学留学を兼ね1年渡米。現地企業ECサイトの企画からCMS開発、デザイン制作なども担当。帰国後、2013年9月より秋田県内企業にてWordPressを中心にウェブサイトを多数構築。 2016年10月秋田県秋田市にて株式会社neccoを設立。WordCamp Kyoto 2017・WordCamp Osaka 2018・各地のJP_Stripes・JP_Stripes Connect 2019、CSS Nite LP64の登壇やAlexa Day2018・2019の運営など社外でも積極的に活動中。 好きなものは猫、読書、建築、Apple。2匹の猫と仲良く暮らしています。秋田の美味しいお米で日々横に成長中。