eureka

【Nuxt】JSON-LD+WAI-ARIAに対応したパンくずリストコンポーネントを作成する

0

パンくずリストを作るときにJSON-LDも作ってとかたまに言われるけど、Nuxtだと結構簡単かも?

環境

Nuxt.js 2.14.0
nuxt-jsonld 1.5.2

パンくずリストのコンポーネントを作成

まずはコンポーネントを作成します。

HTMLを作成

まずはHTMLを書きましょう。
コードをシンプルに保つためCSSについては割愛します。

componentsディレクトリの配下にBreadcrumb.vueというファイルを作成して、下記のように3階層目にいることを想定してHTMLを書いていきます。

<template>
  <nav aria-label="パンくずリスト">
    <ol>
      <li>
        <nuxt-link to="#!">ホーム</nuxt-link>
      </li>
      <li>
        <nuxt-link to="#!">ブログ</nuxt-link>
      </li>
      <li>
        <span aria-current="page">ブログタイトル</span>
      </li>
    </ol>
  </nav>
</template>

navタグにaria-labelでパンくずリストの役割を持っているHTMLであることを明示して、現在地を示すテキストにはaria-currentを入れています。

一旦パンくずを表示させるためにpages/index.vueあたり(表示できればlayoutでもどこでも良いです)に差し込んで表示を確認しましょう。

<template>
  <div>
    <Breadcrumb />
  </div>
</template>

nuxt.config.jscomponents: trueとしていればimport文を書かなくても自動でコンポーネントをインポートしてくれるようになるので設定しておくのがおすすめです。
参考:The components Property – Nuxt.js

export default {
  components: true,
}

v-forでコードを整理

data()でパンくずリストのオブジェクトの配列を定義して、v-forを使ってコードをリファクタします。

nuxt-linkとspanタグの出し分けは次でやるので、一旦2つずつ表示されてしまいますが、そのまま書いておきましょう。

<template>
  <nav aria-label="パンくずリスト">
    <ol>
      <li v-for="(item, index) in breadcrumbs" :key="index">
        <nuxt-link :to="item.path" v-text="item.name" />
        <span aria-current="page" v-text="item.name" />
      </li>
    </ol>
  </nav>
</template>

<script>
export default {
  name: 'Breadcrumb',

  data() {
    return {
      breadcrumbs: [
        {
          name: 'ホーム',
          path: '/',
        },
        {
          name: 'ブログ',
          path: '/blog/',
        },
        {
          name: 'タイトル',
          path: '',
        },
      ],
    }
  },
}
</script>

最後の要素のpathは使わないので空にしましたが、書いても大丈夫です。

nuxt-linkタグとspanタグを分岐する関数を作成

上の階層はリンク付きで、現在地はリンクなしのパンくずリストにしたいので、if文で分岐をさせて出し分けをします。

現在地=配列の最後の要素ということなので、v-forのindexを引数として配列の最後の要素と比較してBoolean型で返す関数を作成します。

  methods: {
    isCurrentPage(index) {
      return this.breadcrumbs.length - 1 === index
    },
  },

関数を作成できたらspanタグとnuxt-linkタグをそれぞれtemplateタグで囲って、span側のtemplateタグにv-if="isCurrentPage(index)"を、nuxt-linkタグ側のtemplateタグにv-elseを追加します。

<template>
  ...
        <li v-for="(item, index) in breadcrumbs" :key="index">
          <template v-if="isCurrentPage(index)">
            <span aria-current="page" v-text="item.name" />
          </template>
          <template v-else>
            <nuxt-link
              :to="item.path"
              v-text="item.name"
            />
          </template>
        </li>
...
</template>

<script>
export default {
  ...,

  methods: {
    isCurrentPage(index) {
      return this.breadcrumbs.length - 1 === index
    },
  },
}
</script>

ここまでうまく表示できているか確認しておきましょう。

pages側からpropsで値を渡す

今のままだとパンくずリスト内で直接dataを定義して固定のテキストを表示しているだけなのでコンポーネントとして使い回すことができませんね。

ページごとに表示させたいデータはページが持つべきなのでpages/index.vueでデータを作成してpropsでコンポーネントに渡すようにしましょう。

components/Breadcrumb.vueのdeta()で定義したbreadcrumbsをpages/index.vueに持ってきましょう。
(表示確認のため3階層のまま使います。)

<template>
  <div>
    <Breadcrumb />
  </div>
</template>

<script>
export default {
  data() {
    return {
      breadcrumbs: [
        {
          name: 'ホーム',
          path: '/',
        },
        {
          name: 'ブログ',
          path: '/blog/',
        },
        {
          name: 'タイトル',
          path: '',
        },
      ],
    }
  },
}
</script>

components/Breadcrumb.vueでpropsを定義して、

export default {
  props: {
    lists: {
      type: Array,
      required: true,
    },
  },
}

pages/index.vueで定義したデータを受け取れるようにします。

<template>
  <div>
    <Breadcrumb :lists="breadcrumbs" />
  </div>
</template>

components/Breadcrumb.vueで使うデータの名前が変わったので修正します。

liのv-forとisCurrentPage関数で使用していたbreadcrumbsをlistsに変更します。

<template>
  ...
        <li v-for="(item, index) in lists" :key="index">
          <template v-if="isCurrentPage(index)">
            <span aria-current="page" v-text="item.name" />
          </template>
          <template v-else>
            <nuxt-link
              :to="item.path"
              v-text="item.name"
            />
          </template>
        </li>
...
</template>

<script>
export default {
  ...,

  methods: {
    isCurrentPage(index) {
      return this.lists.length - 1 === index
    },
  },
}
</script>

これでパンくずリストコンポーネントは完成です!

パンくずリストコンポーネント完成形のコード

部分的に説明してわかりにくかった部分もあったと思うので、ここまでの完成形のコードを記載します。

JSON-LDに対応せず、WAI-AREAのみで良ければこれで完成となります!

<template>
  <nav aria-label="パンくずリスト">
    <ol>
      <li v-for="(item, index) in lists" :key="index">
        <template v-if="isCurrentPage(index)">
          <span aria-current="page" v-text="item.name" />
        </template>
        <template v-else>
          <nuxt-link
            :to="item.path"
            v-text="item.name"
          />
        </template>
      </li>
    </ol>
  </nav>
</template>

<script>
export default {
  name: 'Breadcrumb',

  props: {
    lists: {
      type: Array,
      required: true,
    },
  },

  methods: {
    isCurrentPage(index) {
      return this.lists.length - 1 === index
    },
  },
}
</script>

nuxt-jsonldのインストールと設定

続いてJSON-LDに対応するため、nuxt-jsonldモジュールを使いましょう。

nuxt-jsonldのインストール

npmからインストールします。
nuxt-jsonld – npm

$ npm install nuxt-jsonld
// or
$ yarn add nuxt-jsonld

プラグインの作成と設定

インストールができたらプラグインを作成します。

plugins/jsonld.jsを作成して、下記のように記載しましょう。

import Vue from 'vue'
import NuxtJsonld from 'nuxt-jsonld'

Vue.use(NuxtJsonld)

続いて作成したプラグインをnuxt.config.jsで登録します。

export default {
  ...,
  plugins: ['~/plugins/jsonld'],
}

ここまで出来たらNuxtプロジェクトを再起動します。
(再起動しないとうまく出力されないことがあるので気をつけてください。)

JSON-LDを出力

ここまででJSON-LDを使う準備が整ったので、実際に出力してみましょう。

パンくずリストのオブジェクトにURLを追加

まずはJSON-LDで出力するURLですが、httpから始まる絶対パスでの指定となりますので、ページ側のパンくずリストのオブジェクトにURLを追加します。
ドメインは適宜変更してください。

<script>
export default {
  data() {
    return {
      breadcrumbs: [
        {
          name: 'ホーム',
          path: '/',
          url: 'https://xxx.com/',
        },
        {
          name: 'ブログ',
          path: '/blog/',
          url: 'https://xxx.com/blog/',
        },
        {
          name: 'テストテスト',
          path: '',
          url: `https://xxx.com${this.$route.path}`,
        },
      ],
    }
  },
}
</script>

idによってURLが変わる、動的ルーティングをしているときには上記のようにthis.$route.pathを指定すると現在のパスを取得できます。

JSON-LDを出力する関数を追加

続いて、components/Breadcrumb.vueのscript内にJSON-LDを出力する関数を追記していきます。

export default {
  ...,
  jsonld() {
    const items = this.lists.map((item, index) => ({
      '@type': 'ListItem',
      position: index + 1,
      item: {
        '@id': item.url,
        name: item.name,
      },
    }))
    return {
      '@context': 'http://schema.org',
      '@type': 'BreadcrumbList',
      itemListElement: items,
    }
  },
}

うまく出来ていればheadに出力されているので検証ツールで確認してみましょう。

成功時の出力例(検証ツール)

完成時の全体のコード

ここまでの全体のコードです。
うまくできなかったという方は比較してみてください。

<template>
  <nav aria-label="パンくずリスト">
    <ol>
      <li v-for="(item, index) in lists" :key="index">
        <template v-if="isCurrentPage(index)">
          <span aria-current="page" v-text="item.name" />
        </template>
        <template v-else>
          <nuxt-link
            :to="item.path"
            v-text="item.name"
          />
        </template>
      </li>
    </ol>
  </nav>
</template>

<script>
export default {
  name: 'Breadcrumb',

  props: {
    lists: {
      type: Array,
      required: true,
    },
  },

  methods: {
    isCurrentPage(index) {
      return this.lists.length - 1 === index
    },
  },

  jsonld() {
    const items = this.lists.map((item, index) => ({
      '@type': 'ListItem',
      position: index + 1,
      item: {
        '@id': item.url,
        name: item.name,
      },
    }))

    return {
      '@context': 'http://schema.org',
      '@type': 'BreadcrumbList',
      itemListElement: items,
    }
  },
}
</script>

ページ側のデータは下記の形式です。ページによって変更してください。

<template>
  <div>
    <Breadcrumb :lists="breadcrumbs" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      breadcrumbs: [
        {
          name: 'ホーム',
          path: '/',
          url: 'https://xxx.com/',
        },
        {
          name: '第二階層',
          path: '/second/',
          url: 'https://xxx.com/second/',
        },
        {
          name: '第三階層',
          path: '/third/', // 空でもOK
          url: 'https://xxx.com/second/third/',
        },
      ],
    }
  },
}
</script>

参考にしたサイト

0