Gridsomeでイチからブログを作る - Markdownファイルで記事を作る

前回、前々回と下準備をしました。それらは準備であって人によっては不必要です。料理で言えば「包丁を研ぐ」のような感ですかね。今回こそは料理をして食べれる形、つまり記事部分を表示させてブログの体にもっていくことを目指そうと思います。

記事を表示させるための準備

記事としてのデータの持ち方はいろいろありますが、今回はMarkdownファイルをベースとして表示できるようにします。なお、他の選択肢としてはWordpressを始めとしたCMSでデータを保持、編集しAPI経由で取得するような方法もあります。

記事部分を扱うプラグインは規約としてsourceという命名規則にのっとるように決められています。従来のGridsome v0.6系まではMarkdownファイルを1記事として扱うためのプラグインとして@gridsome/source-filesystemが提供されていました。
2020年2月現在、Blog-Starterもこれがベースとなっています。

Gridsome v0.7系以降では新たに@gridsome/vue-remarkというプラグインも使うことができます。sourceとついていませんが、内部的に@gridsome/source-filesystemを使っているため、これだけでMarkdownで記事を作っていくことができます。

設定方法が少し異なるものの、遜色なく使えます。なによりMarkdownファイル内でVue.jsのSFCを扱えるのは強力です。記事内に自分で開発したVue.jsのコンポーネントを表現の埋めこむことで自由度が飛躍的にアップします。今回はこのVueRemarkを導入してブログにしていきます。

VueRemakの準備

使いかたはここを見るのが早いです。

が、ちゃんとステップバイステップで追っていきましょう。

まずはインストール。

$ yarn add @gridsome/vue-remark

次にgridsome.config.jsを以下のように追記します。

module.exports = {
  plugins: [
    {
      use: '@gridsome/vue-remark',
      options: {
        typeName: 'Post', // 必須。GraphQL上で扱う型定義
        baseDir: './content/posts', // 記事となるmarkdownファイルを置くディレクトリ
        pathPrefix: '/posts', // URLになるパス。必須ではない。
        template: './src/templates/Post.vue' // 記事ページのVueコンポーネントファイルの指定
      }
    }
  ]
}

このオプションは公式にも詳しく書かれています。

{
  typeName: 'Post', // 必須。GraphQL上で扱う型定義
  baseDir: './content/posts', // 記事となるmarkdownファイルを置くディレクトリ
  pathPrefix: '/posts', // URLになるパス。必須ではない。
  template: './src/templates/Post.vue' // 記事ページのVueコンポーネントファイルの指定
}

この設定を説明すると、
Post型のスキーマを作り、
contents/postsにあるmarkdownファイルに従って記事内容を読みこみ
/post/{記事名}
./src/templates/Post.vueのコンポーネントを使って表示する
ということをしています。

さっそくcontents/posts/の中にmy-first-article.mdというファイルを作って試してみます。
記事の内容以外はmarkdownの拡張書式であるfrontmatterに従って設定します。今回は最低限として

---
title: 最初の記事
---
これは私の最初の記事です

と書いてみます。

最初の---の行で囲まれた部分がfrontmatterと言い、メタ情報として扱われます。とりあえずタイトルだけでいいでしょう

このmarkdownを実際に表示する部分となる'./src/templates/Post.vue'を書きます。

<template>
  <article>
    <h1>{{ $page.post.title }}</h1>
    <VueRemarkContent />
  </article>
</template>

<page-query>
query Post  ($id: ID!) {
  post (id: $id) {
    title
    content
  }
}
</page-query>

(この例ではHTMLですが、前回導入したPugをtemplateに指定してもOKです)。

<page-query>内に書いたGraphQLにしたがってデータをロードします。ここの内容がそのままVue.jsのコンポーネント内に$pageにはいります。内部にはVue.jsのcomputeとして扱われるのでthis.$pageで扱うことができます。

<VueRemarkContent />の部分には自動的にMarkdownの本文内がpage-queryの中でcontentとして取得した内容をもとにHTMLとして表示されます。

これで記事単体のページはできました。単体ページだけではアクセスしづらいので、indexとなる記事一覧ページも作りましょう。/src/pages/index.vueを作って

<template>
  <div class="index">
    <g-link v-for="post in $page.posts.edges" :key="post.id" :to="post.node.path">
      <h2> {{ post.node.title }}
    </g-link>
</template>

<page-query>
  query {
    posts: allPost {
      edges {
        node {
          id
          title
          path
        }
      }
    }
  }
</page-query>

と書いてみましょうか。最低限これだけあればOKです。
なお<g-link>はGridsome特有の組み込みコンポーネントで、サイト内リンクを作ります。vue-routerのrouter-link機能とほぼ同一です。サイト内のリンクは<g-link>、サイト外へは通常の<a href>を使います。

ここまでできたら一度表示してみましょうか。
開発中の表示は下記コマンドを打ちます。

$ yarn develop

として、表示されるURL(デフォルトではhttp://localhost:3000)をブラウザで開いてみます。

問題なく表示されていればOKです。記事タイトルと思しきものが1件表示されているので、クリックしたら遷移するでしょうか? なお、VueファイルもMarkdownファイルもHotLoadingが効いているのでファイルを編集して保存すると自動的に反映されます。

タグ対応

記事に分類のためのタグを導入します。要件としては

  • 1記事には複数タグをつけることができる
  • タグ別に一覧ページを表示できる

でしょうか。
gridsome.config.jsのremark部分に以下のように変更します。

module.exports = {
  plugins: [
    {
      use: '@gridsome/vue-remark',
      options: {
        typeName: 'Post',
        baseDir: './content/posts',
        pathPrefix: '/posts',
        template: './src/templates/Post.vue'
      },
      refs: {
        tags: {
          typeName: `Tag`,
          create: true,
        }
      }
    }
  ]
}

このrefsの指定では、TagというSchemaで参照型が作られ、PostのSchemaに含めています。またcreate: trueとしているため/src/templates/Tag.vueを作ることで、Tagごとの一覧ページのテンプレートを表示させることができます。

まずは記事側に

---
title: 最初の記事
tags:
  - tag1
  - tag2
---
これは私の最初の記事です

のようにfrontmatterにYAML記法の配列でタグ名を適当につけます。

あとはPost.vueで指定しているqueryにtagを足し、templateから扱えばいいので

<template>
  <article>
    <h1>{{ $page.post.title }}</h1>
    <ul>
      <li v-for="tag in $page.post.tags" :key="tag.id">
        <g-link :to="tag.path"> {{ tag.title }} </g-link>
      </li>
    </ul>
    <VueRemarkContent />
  </article>
</template>
<page-query>
query Post  ($id: ID!) {
  post (id: $id) {
    title
    content
    tag {
      id
      title
      path
    }
  }
}
</page-query>

とTagを含めるように変更します。

また、Tag.vueを作れば
/tags/tag名で呼ばれるファイルを設定できますので、以下のようなコンポーネントも作ってみましょう。

<template>
  <div class="index">
    <div class="info">
      <p> {{ $page.tag.title }} </p>
    </div>
    <g-link v-for="post in $page.tag.belongsTo.edges" :key="post.id" :to="post.node.path">
      <h2> {{ post.node.title }}
    </g-link>
</template>

<page-query>
query {
  tag: tag(id: $id) {
    title
    belongsTo {
      edges {
        node {
          ... on Post {
            id
            title
            path
          }
        }
      }
    }
  }
}
</page-query>

これでタグ機能を追加できました。

ページングを追加する

ページングを追加するにはqueryをまずページング対応に書きかえます。
この時にpage-infoも一緒に出力します。

それではindex.vueを変更してみましょう。

<template>
  <div class="index">
    <g-link v-for="post in $page.posts.edges" :key="post.id" :to="post.node.path">
      <h2> {{ post.node.title }}
    </g-link>
    <Pager :info="$page.posts.pageInfo"/>
</template>

<page-query>
  query ($page: Int) {
    posts: allPost(perPage: 10, page: $page) @paginate {
      pageInfo {
        totalPages
        currentPage
      }
      edges {
        node {
          id
          title
          path
        }
      }
    }
  }
</page-query>

allPost部分に上記のような引数と@paginateを指定します。同時にpageInfoも出力しておきます。
これをGridsome組み込みコンポーネントのPagerに指定れば自動的にページングを表示してくれます。

クラスやスタイルを変更したい場合や、表示件数や前後移動のための表示文字列も変更できるので、詳細はPaginate data - Gridsomeを参照してください。

まとめ

以上で最小限の記事一覧と記事ページ、タグページの実装ができました。
スターターで作るよりまず簡素に自分で書いてみると何がどうなってるか理解できると思います。

これで最低限のブログの体はできました。あとはこれをベースとしてどんどん機能を拡張していくだけです。
次回以降は必要な機能を追加していきましょう。

ブログリニューアルは式年遷宮 - ブログをVuePressからGridsomeに変えました僕のRelease note[0.38.5] & 僕のRoadMap[0.38.6]