Gridsome|v-ifを使いテンプレート内でデザインを分ける

Profile image of Takashi Hanamura
花村貴史 / Takashi Hanamura 2020/05/04
Eye catching image for this article

Gridsome には「multiple templates for a collection」という仕組みがあって、1 つのコレクションを複数のテンプレートに対応させることができます。

参考:Templates - Gridsome

基本的にコレクション( md ファイル等)とテンプレート( vue ファイル)は 1 対 1 の関係ですが、任意のページだけ専用のテンプレートを適用できるもの、と僕は理解してます。

しかし、Build すると同じ記事データが複数生成されてしまい、記事の重複が起こりました。

dist をそのままサーバーにアップロードしたら SEO 的にどうなの?と考えた末、ひとまず、テンプレート内で「v-if / v-if-else / v-else」を使って乗り切ることにしました。

記事が重複する?

サイト公開当初は 1 種類のコレクション( Markdown )から GraphQL の Filter を使って、3 種類のコンテンツを抽出していました。

// コンテンツ抽出イメージ

コレクション(*.md)
|
|___ Blogコンテンツ(Post.vue)
|___ Graphicコンテンツ(Graphic.vue)
|___ Photoコンテンツ(Photo.vue)
// gridsome.config.js(サイト公開当初の設定、一部抜粋)

plugins: [
  {
    use: '@gridsome/source-filesystem',
    options: {
      path: 'static/posts/**/**/*.md',
      typeName: 'Post',
      remark: {
        plugins: ['@gridsome/remark-prismjs']
      }
    }
  }
]

中略

templates: {
  Post: [
    {
      // ブログ用
      path: '/posts/:slug',
      component: './src/templates/Post.vue'
    },
    {
      // グラフィック作品用
      path: '/graphics/:slug',
      component: './src/templates/Graphic.vue'
    },
    {
      // 写真作品用
      path: '/photos/:slug',
      component: './src/templates/Photo.vue'
    },
  ]
}

↑ をすることで上手いこと生成してくれるかと思いきや・・・以下のようにファイルが生成されてしまいました。

// この重複っぷりったら(笑)

dist
|
|__ posts
|   |__ Blogコンテンツ1
|   |   |__ index.html
|   |__ Blogコンテンツ2
|   |   |__ index.html
|   |__ Graphicコンテンツ1(本来不要)
|   |   |__ index.html
|   |__ Photoコンテンツ1(本来不要)
|   |   |__ index.html
|   |__ Photoコンテンツ2(本来不要)
|       |__ index.html
|
|__ graphic
|   |__ Blogコンテンツ1(本来不要)
|   |   |__ index.html
|   |__ Blogコンテンツ2(本来不要)
|   |   |__ index.html
|   |__ Graphicコンテンツ1
|   |   |__ index.html
|   |__ Photoコンテンツ1(本来不要)
|   |   |__ index.html
|   |__ Photoコンテンツ2(本来不要)
|       |__ index.html
|
|__ photo
    |__ Blogコンテンツ1(本来不要)
    |   |__ index.html
    |__ Blogコンテンツ2(本来不要)
    |   |__ index.html
    |__ Graphicコンテンツ1(本来不要)
    |   |__ index.html
    |__ Photoコンテンツ1
    |   |__ index.html
    |__ Photoコンテンツ2
        |__ index.html

URL としてはすべてのルートが生成されているので、このままサーバーにアップロードしたら同じ記事だらけで「どれが本物か!?」と Google さんは思うでしょう。実際、Search Console には「重複してるよ」って言われました。

というわけで対策を考えます。

対策(1) robots.txt → 失敗

まず思いついたのは robots.txt です。

Gridsome の gridsome-plugin-robots プラグインを使って、特定の URL のみ disallow とすることでした。

// gridsome.config.js(サイト公開当初の設定、一部抜粋)

plugins: [

中略

  {
    use: 'gridsome-plugin-robots',
    options: {
      host: 'https://portfolio.nnamm.com',
      sitemap: 'https://portfolio.nnamm.com/sitemap.xml',
      policy: [
        {
          userAgent: '*',
          allow: '/',
          disallow: [
            '/posts/p0*',
            '/posts/g0*',
            '/photos/b0*',
            '/photos/g0*',
            '/graphics/b0*',
            '/graphics/p0*'
          ]
        }
      ]
    }
  }

しかし、これは disallow が約束されたものではありません。想定していない URL がインデックスされる可能性がありますし、実際されちゃいました。

対策(2) v-if を使って分ける → 成功

もともと Blog/Graphic/Photo それぞれにテンプレート( vue ファイル)を作り、デザインを分けていましたが、1 つの vue ファイルに統合して、処理を切り分ければ良いんじゃなーい?と気づきます。

「これだ!」というわけで、ベストプラクティスかはわかりませんが以下のようにしました。

  1. URL のスラッグの先頭に投稿タイプの文字列を追加( Blog なら b、Graphic なら g、Photo なら p → 例. b0001-200322-start-portfolio-site )
  2. computed で GraphQL からスラッグの頭文字を取得
  3. v-if="postType === 'b'"のようにして処理を分ける
// Post.vue(一部抜粋)

<template>
  <Layout>

    <template v-slot:main-contents>
      <div v-if="postType === 'b'">
         Blog用のデザイン 
      </div>

      <div v-else-if="postType === 'g'">
         Graphic用のデザイン 
      </div>

      <div v-else>
         Photo用のデザイン 
      </div>
    </template>

  </Layout>
</template>

<script>
export default {
  computed: {
    postType: function () {
      return this.$page.article.slug.substr(0, 1)
    }
  }
}
</script>

$pageでGraphQLした結果にアクセスできるのでslugから文字を取得

<page-query>
query Post ($path: String!) {
  article: post (path: $path) {
    id
    title
    createdAt (format: "YYYY.MM.DD")
    updatedAt (format: "YYYY.MM.DD")
    description
    content
    image
    slug
    category
    tags
  }
}
</page-query>

結果、Build すると想定したものだけが生成されていました。スッキリ!

dist
|
|__ posts
    |__ Blogコンテンツ1
    |   |__ index.html
    |
    |__ Blogコンテンツ2
    |   |__ index.html
    |
    |__ Graphicコンテンツ1
    |   |__ index.html
    |
    |__ Photoコンテンツ1
    |   |__ index.html
    |
    |__ Photoコンテンツ2
        |__ index.html

おわりに

Vue.js の条件付きンダリングを使って対応した、というお話でした。

Gridsome はこのような仕様でしたが(僕の使い方が正しいか判断できませんが)、他の SSG はどうなんでしょうね。有名な Nuxt.js や Gatsby.js なんかも同じような感じなのかな。興味が湧いてきました。

とはいえ、まずは無駄なファイルが生成されずスッキリして良かったです。サーバーのディレクトリ構成や URL にこだわりがなければ、この対応で良いと思いますね。

あとは Search Console のインデックスが更新されたらオッケー。しばらく様子見です。

※掲載した写真は春の大阪城公園を RICOH GR で撮影