Vue.jsのSSGフレームワークGridsomeはすごいぞ!!

Vue.jsでSSGをする方法を探していたら、Gridsomeという素晴らしいフレームワークを見つけたので、紹介します。

## Gridsomeとは？
Gridsomeの特徴は、Jamstackなフレームワークであり、すべてのデータソースに対してGraphQLを使ってアクセスできるところにあります。
たとえば、APIやMarkdownで書かれたファイルなんかをデータソースとして扱い、GraphQLを使ってアクセスできるという事です。このあたりは別途、下で解説します。
あと、プリレンダリングでHTMLを事前に生成しておくので（つまり、SSGなので）、「SEO」にも強い。

https://gridsome.org/

私個人の感想としては、GraphQLは人類には早すぎたものだと思っていました。
しかし、いざ使ってみるとこんなに便利なんだ・・・と、考えを改めるほど素晴らしいフレームワークだと感じました。

## プロジェクトを作成して起動してみる
`npm`と`yarn`どちらでも良いのですが、公式では`yarn`をオススメしているので、`yarn`でやります。（なお、事前に自分のPCに`Node.js`を入れておくこと）
まずは、Gridsome用のCLIをGlobalにインストールして、プロジェクトファイルを作成します。

```sh
$ yarn global add @gridsome/cli
$ gridsome create sample-site
```

プロジェクトファイルが作成できたら、以下コマンドで起動してみましょう。

```sh
$ cd sample-site
$ gridsome develop
```

すると、以下のような画面が立ち上がります。
![gridsome_home](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241956/08feda26-7876-f319-4c02-117616fe6903.png)


## Gridsomeの更なる特徴を見てみよう
ここからGridsomeの更なる特徴を見てみます。

### G-LINK
g-linkタグを使用すると、ページを開いた時に、リンク先のページのデータを取得します。
そのため、リンクを押下する前にページの情報を取得しているので、高速にページを閲覧することが可能です。

```html
# src/layouts/Default.vue
<nav class="nav">
  <g-link class="nav__link" to="/">Home</g-link>
  <g-link class="nav__link" to="/about/">About</g-link>
</nav>
```

実際に、ディベロッパーツールから見ても、ページをキャッシュしていることがわかります。
![gridsome_glink-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241956/53b1b455-6f9a-4f3b-5758-be99a3b43d04.png)

### G-IMAGE
デバイスに合わせて、最適化された画像を（解像度や大きさをデバイスに合わせて自動で変更）表示してくれます。
また、g-image内で画像の画質の調整やトリミングも可能です。

```html
# src/pages/Index.vue
<g-image alt="Example image" src="~/favicon.png" width="135" />
```

imgタグと比較しても、「intrinsicsize」ではないことがわかります。
![gridsome_gimg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241956/29897a8f-7929-4a2c-1aa9-781b0c213233.png)
![gridsome_img](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241956/36137bbf-82ea-4b7d-c29a-5475c1796a54.png)

### PagesとTemplate
Gridsomeには、`Pages`と`Template`という概念があります。

`Pages`は、言葉通り単一ページのことで、`src/pages`ディレクトリ内に`vueファイル`を配置することで、動的にURLが生成されます。
公式にも記載がありますが、以下のようにURLが生成されます。

- src/pages/Index.vue = /（フロントページ）
- src/pages/AboutUs.vue = /about-us/
- src/pages/about/Vision.vue = /about/vision/
- src/pages/blog/Index.vue = /blog/

`Template`は、コレクション内のノードの単一ページを表します。
コレクションとは、データの塊を表し、その1つ1つをノードと表現します。
![gridsome_collections.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241956/594db92b-76fa-e8cf-787f-004ff70d5f5c.png)

ブログサイトなんかを例にすると、投稿データがコレクション、投稿データの中の1つの記事がノードになります。

## Markdownファイルから簡単なブログサイトを作ってみる
では、さっそく試してみましょう。
MarkdownファイルをVueコンポーネントを使用してページを作成するには、以下プラグインが必要なので、こちらをインストールします。

```sh
$ yarn add @gridsome/vue-remark
```

次に、プロジェクト設定ファイル（`gridsome.config.js`）に上記でインストールしたプラグインを使用するように設定します。

```js
module.exports = {
  siteName: 'Sample Site',
  templates: {
    Tag: "/tags/:id" // タグを使用した時のテンプレートファイル名とURLPath
  },
  plugins: [
    {
      use: '@gridsome/vue-remark',
      options: {
        typeName: 'Post', // GraphQLスキーマのタイプ名
        baseDir: './blog', // Markdownファイル配置場所
        pathPrefix: '/', // URLPathのプレフィックス
        template: './src/templates/Post.vue', // テンプレートファイル名
        refs: {
          tags: { // タグを使用する
            typeName: 'Tag', 
            create: true // tagsからタグのコレクションを生成
          }
        }
      }
    }
  ]
}
```

Markdownで書かれたファイルを以下のように`メタデータ`を埋め込んで、いくつか`/blog`直下に保存しておきます。

```md
---
title: "OSSのライセンスについて少し調べてみた"
description: "GitHubに落ちているライブラリ。さくっと使えて便利なのですが、ふと「ライセンス」のことも気になったので、調べたことを以下に纏めておこうと思います。  "
tags: ['OSS', 'ライセンス']
date: 2020-07-06
---

## OSSのライセンスについて少し調べてみた
GitHubに落ちているライブラリ。  
さくっと使えて便利なのですが、ふと「ライセンス」のことも気になったので、調べたことを以下に纏めておこうと思います。  
・・・以下略・・・

```

次に、投稿データ（コレクション）を表示するページを作ります。
`Pages`なので、`/src/pages/Blog.vue`で作成します。

```html
<template>
  <Layout>
    <h1>Blog</h1>
    <p>記事の一覧です。</p>
    <article class="blog-article" v-for="post in $page.allPost.edges" :key="post.node.id">
      <h2>{{post.node.title}}</h2>
      <span v-for="tag in post.node.tags" :key="tag.id">
        <g-link class="blog-article-glink" :to="tag.path">#{{tag.title}}</g-link>
      </span>
      <div>{{post.node.description}}</div>
      <div>
        <g-link :to="post.node.path">本文を読む</g-link>
      </div>
    </article>
  </Layout>
</template>

<page-query>
  query ($page: Int) {
    allPost (perPage: 5, page: $page) @paginate {
      pageInfo {
        totalPages
        currentPage
      }
      edges {
        node {
          id
          title
          description
          date (format: "YYYY/MM/DD")
          tags {
            id
            title
            path
          }
          path
        }
      }
    }
  }
</page-query>

<script>
import { Pager } from 'gridsome'

export default {
  metaInfo: {
    title: 'Blog'
  },
  components: {
    Pager
  }
}
</script>

<style>
  /* 省略 */
</style>
```

`<page.query>`で囲われたところが、GraphQLのQuery部分です。
Gridsomeは、GraphQLのPlayGroundを起動させることができるので、ここでQueryを実行して、取得するデータ内容を確認できます。
GraphQLを使用することで、ページネーションも楽々に実装できます。

![gridsome_playground.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241956/e81a935a-23d4-c5b9-a718-88f115951d4d.png)

続いて、投稿データの中の1つの記事（ノード）を表示するページを作成します。
`Template`なので、`/src/templates/Post.vue`で作成します。

```html
<template>
  <Layout>
    <article>
      <VueRemarkContent />
      <aside>
        <span v-for="tag in $page.post.tags" :key="tag.id">
          <g-link class="blog-article-glink" :to="tag.path">#{{tag.title}}</g-link>
        </span>
      </aside>
    </article>
  </Layout>
</template>

<page-query>
  query Post ($id: ID!) {
    post (id: $id) {
      title
      description
      content
      path
      date (format: "YYYY/MM/DD")
      tags {
        id
        title
        path
      }
    }
  }
</page-query>

<script>
export default {
  metaInfo() {
    return {
      title: this.$page.post.title,
      meta: [
        { name: 'description', content: this.$page.post.description },
      ]
    }
  }
}
</script>

<style>
  /* 省略 */
</style>
```

`VueRemarkContent`タグを使用すれば、Markdownの内容（Content）をHTMLに変換して表示できます。
一緒に取得してきた`title`や`description`は、メタタグに使用します。

最後に、サイト内共通で使用している`/src/layouts/Default.vue`に`Blog`へのリンクを追加します。

```html
<g-link class="nav__link" to="/blog/">Blog</g-link>
```

ここまでで、一通りのページが表示可能ですが、タグの一覧ページも作りたい場合は、`/src/templates/Tag.vue`を作成します。

```html
<template>
  <Layout>
    <h1>#{{ $page.tag.title }}の一覧</h1>
    <article class="blog-article" v-for="tag in $page.tag.belongsTo.edges" :key="tag.node.id">
      <h2>{{tag.node.title}}</h2>
      <div>{{tag.node.description}}</div>
      <div>
        <g-link :to="tag.node.path">本文を読む</g-link>
      </div>
    </article>
  </Layout>
</template>

<page-query>
query Tag($id: ID) {
  tag (id: $id) {
    title
    belongsTo {
      edges {
        node {
          ... on Post {
            id
            title
            description
            date (format: "YYYY/MM/DD")
            path
          }
        }
      }
    }
  }
}
</page-query>

<style>
  /* 省略 */
</style>
```

最後に以下コマンドでビルドします。  すると`dist`の下にHTMLなどが作られます。

```sh
gridsome build
```

ビルドで作られたHTMLなどを`GitHub Pages`や`AWS S3`などに配置することで、HTMLを参照できます。
たとえば、`GitHub Pages`であれば、`gridsome.config.js`に、以下を追加して、ビルドしたものをそのままリポジトリにアップすることでサイトを閲覧できます。

```
  pathPrefix: '/pages/{ユーザー名}/{リポジトリ名}',
  outputDir: 'docs',
```

作成したサイトは以下のような感じ。
<img width="700" alt="gridsome-sample-site.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241956/cfb82ebe-6eee-2939-042a-8426c6993388.gif">


## まとめ
Gridsomeのすごいところは、すべてのデータソースに対してGraphQLを使ってアクセスできるところにあると思います。正直、GraphQLにこんな使い方ができるなんて私は知りませんでした。（考えを改めます:bow_tone1:）
加えて、SSGなのでSEOに強く、自前でサーバーを用意する必要がないところも素敵です。Nuxt.jsほどモダンなフレームワークを使いたくないって時の選択肢にもなると思います。
是非、みなさんGridsomeの凄さを体験してみてください。

[:christmas_tree: FORK Advent Calendar 2020](/advent-calendar/2020/fork)
:arrow_left: 18日目 [Reactで神経衰弱を作ってみた](https://qiita.com/ktn/items/054140bb496a707fafbf) @ktn 
:arrow_right: 20日目 [MediaDevicesとWeb Audio API Vue.jsとThree.js で 音声の波形表示](https://qiita.com/shogun-fork/items/20e2a8467c63abe80624) @shogun-fork