🗺

NuxtでGeneratした時に手軽にsitemapを作る

2022/04/18に公開

前提

Nuxt(@2.15.8)で静的なサイトを制作しています。ヘッドレスCMSを使って各ページを生成する形のシンプルなサイトです。
今回は動的なページのリストページがあるので、動的なルートの生成は各ページからリンク(NuxtLink)を辿って、Nuxtがいい感じに生成してくれます。

使用技術

  • フレームワーク:Nuxt.js(@2.15.8)
  • モジュール: nuxtjs/sitemap(@2.4.0)

事前確認

@nuxtjs/sitemapを導入するにあたり、調べてみたとろこ、動的なページの場合、nuxt.config.jsでaxiosしてroutesを生成する、という記事が出てきました。

例:axiosを使ってroutesを返す
export default {
  modules: [
    '@nuxtjs/sitemap'
  ],
  sitemap: {
    hostname: 'https://example.com',
    exclude: [
      '/company'
    ],
    routes: async () => {
      // APIから動的なルートの情報を取得
      const res = await axios.get(
        process.env.API_URL,
        {
          headers: {
            'X-API-KEY': process.env.API_KEY
          }
        }
      )
      // 動的なルートを配列として返す
      return res.data.contents.map((info) => `/info/${info.id}`)
    }
  }
}

https://cly7796.net/blog/javascript/generate-sitemap-xml-with-nuxt-js/

今回の場合の懸念
ブログの様なサイトであれば上記の形でも問題ないと思いますが、今回のサイトではカスタムのCMSということもあり、取得したデータを編集してページを生成しています。
ですので、出来ればNuxtのルーティングをそのまま使えないかと思いました。何か修正した時にも修正箇所が複数、それも別に実装されているのは良い状態では無いと考えました。
また、個別の理由ですが、nuxt.config.jsでfetchを使えなかったので、別のやり方を探し始めました。

解決策を探してみる

何か良い方法は無いかと探していたら下記のサイトを見つけました。

https://dev.to/andynoir/sitemap-for-dynamic-routes-in-nuxtjs-4b96

ポイント
実務でもNuxtを使っているのですが、hookというのを初めて見ました。

コード全体(上記より引用)
import { Module } from '@nuxt/types'

const generator: Module = function () {
  this.nuxt.hook('generate:done', async (context: any) => {
    const routesToExclude: string[] =
    process.env.NUXT_ENV_EXCLUDE_ROUTES
      ? process.env.NUXT_ENV_EXCLUDE_ROUTES.split(',') : []
    const allRoutes: string[] = await Array.from(context.generatedRoutes)
    const routes: string[] = await allRoutes.filter((route: string) => !routesToExclude.includes(route))
    this.nuxt.options.sitemap.routes = await [...routes]
  })
}

export default generator

hookについて調べると下記のサイトが参考になりました。

https://www.memory-lovers.blog/entry/2021/07/30/113000

実装してみる

先程のコードを今回のプロジェクトにコピペしてみました。
modluesフォルダにgenerator.tsを作ってコードにconsole.logを使って何をしているか確認してみました。contextにはgenerateの情報やNuxt自身の情報などほぼ全ての情報が格納されていました。そのなかでcontext.generatedRoutesには生成されたルートが格納されていました。
整理すると下記の用になりました

generator.ts
import { Module } from '@nuxt/types'

const generator: Module = function () {
  // generateの完了後に呼ばれるフック
  this.nuxt.hook('generate:done', async (context: any) => {
    // 
    const routes = []
    // 生成されたルート(Set)からルートを取得
    for (const route of context.generatedRoutes) {
      // routesにパスを追加
      routes.push({ url: route })
    }
    // サイトマップのroutesに追加
    this.nuxt.options.sitemap.routes = routes
  })
}

export default generator

考え方としてはとてもシンプルで、generateされたルートを@nuxtjs/sitemapのroutesに設定しているだけです。他の記事であったような、axiosを使った場合との違いは、APIで取得する部分をNuxtが生成したルートを使っている点だけです。

モジュールが必須なのか

先程のサイトだとモジュール化してbuildModulesで読み込んでいます。ただ、内容的にモジュール化するほどでは無いので、hookの仕様を確認してnuxt.config.jsに記述してみました。
下記の書き方でも問題なく動作しました。違いとしてはnuxtへのアクセスをcontextを通して行っている点です。(ただ、モジュールの方も同じアクセスは可能だと思います)

nuxt.config.js
export default {
  // sitemapの設定
  sitemap: {
    hostname: 'https://example.com',
    exclude: [
      '/company',
    ],
  },

  // hook
  hooks: {
    generate: {
      done(context) {
        // generate後にサイトマップに使う動的ルートを取得して、サイトマップに追加する
        const routes = []
        for (const route of context.generatedRoutes) {
          // 除外以外を処理 (除外はsitemapの方で指定可能)
          if (!context.nuxt.options.sitemap.exclude.includes(route)) {
            routes.push({
              url: route
            })
          }
        }
        // サイトマップのroutesに追加
        context.nuxt.options.sitemap.routes = routes
      }
    }
  },
}

まとめ

動的なルートからSitemapを生成する方法だと、axiosを使った記事が多かったので、今回の方法を知れてよかったと思っています。
ただ、Nuxt3ではまた違ってくると思うので、使える期間は短いかも知れません。
また、今回のプロジェクトの場合だと問題になりませんでしたが、SitemapのlastModが今回の方法だと一捻り必要そうだなと思いました。

Discussion