世界を旅して暮らしたい放浪エンジニアブログ

Gridsomeを利用して簡単なサイトを作成しよう

静的ジェネレータであるGridsomeの調査として簡易的なサイトの構築を試してみたいと思います。ローンチ直後に比べてプラグインなども揃ってきていてブログやHPであれば十分構築できると思います。今後の開発でGridsomeが導入できるように基礎知識をつけていきたいと思います。

[ 目次 ]

はじめに

こんにちは、香港に住んでいるWEBデベロッパーのなかむ(@nakanakamu0828)です。

今回は、静的ジェネレータであるGridsomeを試していきたいと思います。

Gridsome

【内容】

  • Gridsomeの入門としてGetting started に沿って開発環境を構築する
  • プラグインを導入し、UIの変更(Tailwind CSSを利用)や投稿機能の追加を行う
  • 成果物をNetlifyにデプロイする

【対象者】

  • Gridsomeを利用してホームページやコーポレートサイト、ブログを構築したいGridsome初学者。または、フロントエンドを学んでいきたいバックエンドエンジニア

【注意事項】

  • 進めていく前提としてnpm, yarnがインストールされていることを想定しています。
  • 本投稿でTailwind CSSのプラグインを導入していきます。Tailwind CSS導入後はTailwind CSSでUIを構築しています。
  • Netlifyの新規登録や利用方法については述べません。デプロイ部分のみになります。

Gridsomeとは

GatsbyJSにインスパイアされたVue.jsでUI部分を実装できる静的サイトジェネレーターです。
特徴については公式サイトをご確認ください。

gridsome 特徴

gridsome work flow

Gridsome入門

Gridsomeの公式サイト - Getting started を参考に進めていきます。

インストール

■ npmを利用する場合

$ npm install --global @gridsome/cli

■ yarnを利用する場合

$ yarn global add @gridsome/cli

プロジェクト作成

$ gridsome create gridsome-site-starter
$ cd gridsome-site-starter
$ gridsome develop

http://localhost:8080/ でブラウザからアクセスできれば成功です。
以下の初期画面が表示されることを確認してください。

gridsome デモ画面

ページを追加する

既に、トップページとAboutページが用意されています。
今回はTerm(規約)ページを追加して見ましょう。

$ # gridsome-site-starterディレクトリ配下にいる想定です。
$ touch src/pages/Term.vue

Term.vueの内容は以下のような内容にします。

<template>
  <Layout>
    <h1>Term</h1>
    <p>利用規約・・・・</p>
  </Layout>
</template>

<script>
export default {
  metaInfo: {
    title: 'Term'
  }
}
</script>

ヘッダーにTermのリンクを追加します。
src/layouts/Default.vueのtemplate内を変更しましょう。

<template>
  <div class="layout">
    <header class="header">
      <strong>
        <g-link to="/">{{ $static.metaData.siteName }}</g-link>
      </strong>
      <nav class="nav">
        <g-link class="nav__link" to="/">Home</g-link>
        <g-link class="nav__link" to="/about">About</g-link>
        <!--  この1行を追加 -->
        <g-link class="nav__link" to="/term">Term</g-link>
        <!--  ここまで -->
      </nav>
    </header>
    <slot/>
  </div>
</template>

ここまでの修正によりヘッダーで画面遷移することができるようになります。

Gridsomeのプラグインを利用する

公式サイト - PLUGINS にプラグインが用意されています。今回はその中から何個かピックアップして導入してみます。

gridsome-plugin-tailwindcss

gridsome-plugin-tailwindcssは、私のブログでも何度か取り上げてきた Tailwind CSS を組み込む為のプラグインです。

過去のブログ記事は以下からご確認ください。

Tailwind CSSについての投稿

それではgridsome-plugin-tailwindcssをインストールしていきましょう。
以下のコマンドからプラグインをインストールします。

■ npmを利用する場合

$ npm install --save-dev gridsome-plugin-tailwindcss

■ yarnを利用する場合

$ yarn add --dev gridsome-plugin-tailwindcss

続けて Tailwind CSSの初期化 を行います。

■ npmを利用する場合

$ npm tailwind init  [filename]

■ yarnを利用する場合

$ yarn tailwind init [filename]

今回はfilenamesrc/tailwind.jsとし、gridsomeのsrcディレクトリ直下にtailwind.jsを配置しました。
続いてエントリーポイントになるCSSファイルを作成します。Tailwind CSSのUse Tailwind in your CSSの部分です。

$ mkdir -p src/assets/
$ touch src/assets/tailwind.css

内容は以下のようなtailwindの読み込みを記述します。

@tailwind preflight;

@tailwind components;

@tailwind utilities;

作成したtailwind.cssは、src/main.jsにてimportしましょう。

import '~/assets/tailwind.css'

最後に、gridsome-plugin-tailwindcssプラグインの読み込み設定を追加します。
gridsome.config.jsを以下のように修正しましょう。

module.exports = {
  siteName: 'Gridsome',
  plugins: [
    {
      use: 'gridsome-plugin-tailwindcss',
      options: {
        config: './src/tailwind.js'
      }
    }
  ]
}

これでTailwind CSSが読み込まれるようになります。gridsome developを再度起動して確認しましょう。
(ここではTailwind CSSを利用したHTMLコーディングは省略します)

これ以降に出てくるHTMLでは、Tailwind CSSのclassをサンプルとして利用しています

gridsome-plugin-purgecss

Tailwind CSSの未使用のclassをbuild時に除外し、cssのファイルサイズを圧縮したい場合にpurgecssが利用できます。
今回は、gridsome-plugin-purgecssプラグインを利用してpurgeを行います。

purgecssについてや導入については過去のブログ記事もご確認ください。

Laravel & Tailwind CSS のプロジェクトにPurgecssを導入する

それではgridsome-plugin-purgecssをインストールしていきましょう。
以下のコマンドからプラグインをインストールします。

■ npmを利用する場合

$ npm install --save-dev gridsome-plugin-purgecss

■ yarnを利用する場合

$ yarn add --dev gridsome-plugin-purgecss

続いて、gridsome.config.jspurgecssの設定を追加していきます。
以下のように修正しましょう。

class TailwindExtractor {
  static extract(content) {
    return content.match(/[A-z0-9-:\/]+/g) || []
  }
}

module.exports = {
  siteName: 'Gridsome',
  plugins: [
    {
      use: 'gridsome-plugin-tailwindcss',
      options: {
        config: './src/tailwind.js'
      }
    },
    {
      use: 'gridsome-plugin-purgecss',
      options: {
        content: [
            './src/**/*.vue',
            './src/**/*.js',
            './src/**/*.jsx',
            './src/**/*.md'
          ],
          extractor: TailwindExtractor,
          extensions: ['vue', 'js', 'jsx', 'md']
      }
    }
  ]
}

ファイルサイズが圧縮されたかどうかは、gridsome builddist配下にbuildした静的ファイルを生成し確認できます。Tailwind CSSでUIを構築した後、purgecssの有無でjs, cssのファイルサイズを確認してみましょう。

@gridsome/source-filesystem

投稿データをmarkdownファイルで管理できるように@gridsome/source-filesystemプラグインを導入します。markdownを利用するので@gridsome/transformer-remarkプラグインを一緒にインストールしましょう。
以下のコマンドからプラグインをインストールします。

■ npmを利用する場合

$ npm install --save-dev @gridsome/source-filesystem @gridsome/transformer-remark

■ yarnを利用する場合

$ yarn add --dev @gridsome/source-filesystem @gridsome/transformer-remark

続いて、gridsome.config.js@gridsome/source-filesystemの設定を追加し、ブログ投稿ページのルーティング設定やファイルの配置場所、UIテンプレートとして利用するVueファイルを定義します。
以下のように修正しましょう。

module.exports = {
  siteName: 'Gridsome',
  plugins: [
    ・・・
    {
      use: '@gridsome/source-filesystem',
      options: {
        // 投稿ファイルは、blogディレクトリ配下に配置
        path: 'blog/*.md',
        // 投稿ページのUIテンプレートは、BlogPost.vueとします
        typeName: 'BlogPost',
        // URLパスは、/blog/:slagとしてアクセス可能
        route: '/blog/:slug'
      }
    },
    ・・・
  ],
  transformers: {
    remark: {
      // 外部リンクは target="_blank"になるように設定
      externalLinksTarget: '_blank',
      // SEO対策として外部リンクは rel="nofollow noopener noreferrer"になるように設定
      externalLinksRel: ['nofollow', 'noopener', 'noreferrer'],
      plugins: [
      ]
    }
  }
}

次にブログ投稿ページのUIテンプレートを作成しましょう。
公式サイト - Templates を参考に進めていきます。
以下のコマンドからsrc/templates/BlogPost.vueを作成してください。
BlogPostという名称はgridsome.config.jstypeNameと一致するようにします。

$ touch src/templates/BlogPost.vue

src/templates/BlogPost.vueは以下のように実装します。

<template>
  <Layout>
    <div class="container mx-auto sm:py-10 flex justify-center">
        <div class="max-w-lg rounded overflow-hidden shadow-lg">
            <div class="px-6 py-4 border-b mb-2">
                <p class="my-1 text-sm text-grey-darker">
                    <span class="text-grey-darker text-sm">
                        {{ $page.blogPost.createdAt }}
                        <svg class="h-3 w-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1792 1792" fill="currentColor"><path d="M1639 1056q0 5-1 7-64 268-268 434.5t-478 166.5q-146 0-282.5-55t-243.5-157l-129 129q-19 19-45 19t-45-19-19-45v-448q0-26 19-45t45-19h448q26 0 45 19t19 45-19 45l-137 137q71 66 161 102t187 36q134 0 250-65t186-179q11-17 53-117 8-23 30-23h192q13 0 22.5 9.5t9.5 22.5zm25-800v448q0 26-19 45t-45 19h-448q-26 0-45-19t-19-45 19-45l138-138q-148-137-349-137-134 0-250 65t-186 179q-11 17-53 117-8 23-30 23h-199q-13 0-22.5-9.5t-9.5-22.5v-7q65-268 270-434.5t480-166.5q146 0 284 55.5t245 156.5l130-129q19-19 45-19t45 19 19 45z"></path></svg>
                        {{ $page.blogPost.updatedAt }}
                    </span>
                </p>
                <h1 class="my-2">{{ $page.blogPost.title }}</h1>
                <p class="my-2 text-sm text-grey-darker">{{ $page.blogPost.description }}</p>
                <div class="my-1">
                    <span
                        class="inline-block bg-grey-lighter rounded-full px-3 py-1 text-sm font-semibold text-grey-darker mr-2"
                        v-for="tag in tags"
                        :key="tag"
                        v-text="`#${tag}`"
                    />
                </div>
                <g-image class="mt-5" :src="$page.blogPost.image"/>
            </div>
            <div class="px-6 py-4 content" v-html="$page.blogPost.content" />
        </div>
    </div>
  </Layout>
</template>

<script>
export default {
  metaInfo () {
    return {
      title: this.$page.blogPost.title
    }
  },
  computed: {
    tags () {
      return this.$page.blogPost.tags.split(' ');
    },
    totalCount () {
      return this.$page.allPost.totalCount
    }
  },
}
</script>

<page-query>
  query BlogPost ($path: String!) {
    blogPost (path: $path) {
      title
      description
      createdAt (format: "YYYY.MM.D")
      updatedAt (format: "YYYY.MM.D")
      content
      image
      tags
    }
  }
</page-query>

<style>
.content {
    @apply text-base;
}
.content > h1 {
    font-size: 1.5rem;
    @apply py-2;
}
.content > p {
    @apply text-grey-darker mb-2;
}
</style>

Home画面にブログの一覧を表示します。Home画面からブログ投稿ページに遷移できるようにしましょう。
src/pages/Indev.vueの内容を以下のように変更します。

<template>
  <Layout>
    <div class="container mx-auto p-10 flex justify-center">
      <div class="max-w-lg">

        <div
          class="w-full lg:flex"
          v-for="{ node } in $page.allBlogPost.edges"
          :key="node._id"
        >
          <div class="border border-grey-light bg-white rounded p-4 flex flex-col justify-between leading-normal">
            <p class="my-1 text-sm text-grey-darker">
                <span class="text-grey-darker text-sm">
                    {{ node.createdAt }}
                    <svg class="h-3 w-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1792 1792" fill="currentColor"><path d="M1639 1056q0 5-1 7-64 268-268 434.5t-478 166.5q-146 0-282.5-55t-243.5-157l-129 129q-19 19-45 19t-45-19-19-45v-448q0-26 19-45t45-19h448q26 0 45 19t19 45-19 45l-137 137q71 66 161 102t187 36q134 0 250-65t186-179q11-17 53-117 8-23 30-23h192q13 0 22.5 9.5t9.5 22.5zm25-800v448q0 26-19 45t-45 19h-448q-26 0-45-19t-19-45 19-45l138-138q-148-137-349-137-134 0-250 65t-186 179q-11 17-53 117-8 23-30 23h-199q-13 0-22.5-9.5t-9.5-22.5v-7q65-268 270-434.5t480-166.5q146 0 284 55.5t245 156.5l130-129q19-19 45-19t45 19 19 45z"></path></svg>
                    {{ node.updatedAt }}
                </span>
            </p>
            <router-link
              :to="node.path"
              class="no-underline text-black font-bold text-xl py-1"
            >
              <h3 v-html="node.title"/>
            </router-link>
            <p class="text-grey-darker text-base mb-2"
              v-html="node.description"
            />
            <div class="my-1">
                <span
                    class="inline-block bg-grey-lighter rounded-full px-3 py-1 text-sm font-semibold text-grey-darker mr-2"
                    v-for="tag in node.tags.split(' ')"
                    :key="tag"
                    v-text="`#${tag}`"
                />
            </div>
          </div>
        </div>

      </div>
    </div>
  </Layout>
</template>

<script>
export default {
  metaInfo: {
    title: 'Home'
  }
}
</script>

<page-query>
  query Home ($page: Int) {
    allBlogPost (page: $page) {
      edges {
        node {
          _id
          title
          createdAt (format: "YYYY.MM.D")
          updatedAt (format: "YYYY.MM.D")
          description
          path
          tags
        }
      }
    }
  }
</page-query>

最後にblogディレクトリ配下に投稿用のmarkdownファイルを作成しましょう。

$ mkdir -p blog
$ # 画像の配置場所も用意
$ mkdir -p blog/images
$ touch blog/`date +'%Y-%m-%d'`_first_blog.md

投稿ファイルは以下のような内容にします。
画像を作成し設定していることに注意してください。

---
title: ブログ初投稿です。
createdAt: 2019-03-04 00:00:00
updatedAt: 2019-03-04 00:00:00
description: "初めてのブログ投稿です。初めてのブログ投稿です。初めてのブログ投稿です。初めてのブログ投稿です。初めてのブログ投稿です。初めてのブログ投稿です。"
image: "./images/gridsome_starter_blog_image1.png"
slug: first-post
tags: aaa bbb ccc
---

# ブログを初めて投稿1
テストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテスト。  
[Google](https://www.google.co.jp/)
  
# ブログを初めて投稿2
テストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテスト。  
[Yahoo](https://www.yahoo.co.jp/)

# ブログを初めて投稿3
テストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテスト。  

# ブログを初めて投稿4
テストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテスト。  

# ブログを初めて投稿5
テストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテストテスト。  

ここまで作成できると以下のようにHome画面の一覧から投稿画面への遷移ができるようになります。

@gridsome/remark-prismjs

@gridsome/remark-prismjsプラグインを導入することで、Prismのコードハイライトが実現できます。こちらも導入してみましょう。

以下のコマンドからプラグインをインストールします。

■ npmを利用する場合

$ npm install --save-dev @gridsome/remark-prismjs

■ yarnを利用する場合

$ yarn add --dev @gridsome/remark-prismjs

続いてgridsome.config.js@gridsome/remark-prismjsの設定を追加します。
@gridsome/source-filesystemremarkプラグインとして@gridsome/remark-prismjsを追加する形になります。
以下のようにgridsome.config.jsを修正しましょう。

module.exports = {
  siteName: 'Gridsome',
  plugins: [
    ・・・
    {
      use: '@gridsome/source-filesystem',
      options: {
        // 投稿ファイルは、blogディレクトリ配下に配置
        path: 'blog/*.md',
        // 投稿ページのUIテンプレートは、BlogPost.vueとします
        typeName: 'BlogPost',
        // URLパスは、/blog/:slagとしてアクセス可能
        route: '/blog/:slug',
        
        // 以下を追加 - この設定の場合投稿ページにてprismjsが適応されます
        remark: {
          plugins: [
            '@gridsome/remark-prismjs'
          ]
        }
        // ここまで
        
      }
    },
    ・・・
  ],
 ・・・
}

このままだとスタイルが適応されません。
@gridsome/remark-prismjsインストール時にテーマもインストールされています。今回は prismjs/themes/prism-okaidia.cssを利用します。
src/main.jsにimportしましょう。

import 'prismjs/themes/prism-okaidia.css'

他にもprism-themesからテーマを選べます。自分が好きなテーマを組み込みましょう。

注意事項)
purgecssを導入している場合、prismテーマのcssがパージされてしまいます。gridsome.config.js内にあるgridsome-plugin-purgecssの設定を以下のように変更してください。

module.exports = {
  siteName: 'Gridsome',
  plugins: [
    ・・・
    {
      use: 'gridsome-plugin-purgecss',
      options: {
        content: [
            './src/**/*.vue',
            './src/**/*.js',
            './src/**/*.jsx',
            './src/**/*.md',
            // 以下の1行を追加
            'node_modules/prismjs/**/*.js'
            // ここまで
          ],
          extractor: TailwindExtractor,
          extensions: ['vue', 'js', 'jsx', 'md']
      }
    }
  ]
}

この設定をすることでprismjsのjavascriptから利用されているclassを検索しパージ対象外とします。

@gridsome/plugin-sitemap

サイトマップを生成するプラグインです。こちらも導入してみましょう。
以下のコマンドからプラグインをインストールします。

■ npmを利用する場合

$ npm install --save-dev @gridsome/plugin-sitemap

■ yarnを利用する場合

$ yarn add --dev @gridsome/plugin-sitemap

続いて、gridsome.config.jsplugin-sitemapの設定を追加していきます。
以下のように修正しましょう。
configにsiteUrlを追加し、サイトのベースURLを設定しましょう。

module.exports = {
  siteName: 'Gridsome',
  siteUrl: 'https://gridsome-starter-site.netlify.com', // サイトのベースURLを設定
  plugins: [
    ・・・
   {
      use: '@gridsome/plugin-sitemap',
      options: {
        cacheTime: 600000,
        exclude: ['/exclude-me'],
        config: {
          '/blog/*': {
            changefreq: 'weekly',
            priority: 0.5
          },
          '/about': {
            changefreq: 'monthly',
            priority: 0.7
          },
          '/term': {
            changefreq: 'monthly',
            priority: 0.7
          }
        }
      }
    }
    ・・・
  ]
}

サイトマップの生成については、gridsome buildでbuildを行うとdist/sitemap.xmlが作成されるので、そちらから確認が可能になります。

Google Fontsの導入

Google Fonts のページから利用したいフォントのURLをコピーします。
src/main.jsにheadタグのlinkにGoogle FontsのURLを追加すれば読み込みが完了です。

export default function (Vue, { head }) {
  head.link.push({
    rel: 'stylesheet',
    href: 'https://fonts.googleapis.com/css?family=Roboto'
  })
}

後は、classで指定したフォントを定義し、html内のclass属性に設定すればフォントが反映されます。

Netlifyへのデプロイ

作成したGridsomeのサイトをNetlifyにデプロイします。githubと連携してNetlifyにデプロイしてみましょう。

netlify.tomlの作成

netlify.tomlにデプロイ時のbuild設定を記述します。

[build]
  publish = "dist"
  command = "yarn build"

githubにpush

github上でリポジトリを作成しpushしてください。
私のリポジトリの場合、以下のようなコマンドでpushまで行いました。

git remote add origin git@github.com:nakanakamu0828/gridsome-starter-site.git
git push -u origin master

※ 各環境に合わせてgithubのURLは変更してください。

Netlifyの管理画面からgithubと連携してデプロイ

Netlifyのダッシュボードから"New site from Git"ボタンを押下し、デプロイするgithubリポジトリを選択することでデプロイが開始されます。

Netlify deploy step1

Netlify deploy step2

Netlify deploy step3

成果物

以下が今回作成したGridsomeのリポジトリになります。
ソースの詳細はこちらをご確認ください。

Gridsome Starter Site

出来上がったサイトはこちらです。

Gridsome Starter Site

実際のデモ画面はデザインを調整しています。

簡易的なサイトですが、ChromeでLighthouseを測定してみるとかなり結果がよかったです。"Progressive Web App"の点数が低いですが、gridsomeのPWA機能が待ち遠しいところです。

Gridsome Lighthouse結果

最後に

今回はGridsomeを試してきました。静的サイトの構築であればNuxt.jsを利用するよりGridsomeの方が簡単に導入できるのではないでしょうか?

  • HedressCMSを利用した投稿データの連携
  • 投稿ページのページング
  • -タグやカテゴリの管理とタグページ、カテゴリーページの作成

は行っていません。引き続き実装方法を調査したいと思います。

前のページ

次のページ

Profile

なかむ🇭🇰Webデベロッパー

なかむ🇭🇰Webデベロッパー

香港在住4年目になるWEBエンジニアのなかむです。 現在は、LaravelやRailsを利用したWEB開発を中心にエンジニアをしています。 顧客は全て日本の企業になります。リモート開発にて各企業様の支援を行なっております

プロフィール詳細はこちら

Latest Posts