Dev

NuxtのSSGで一部のページのルートが生成されない

投稿日: 2021.05.05
更新日: 2021.05.05
4分
NuxtのSSGで一部のページのルートが生成されない

副業で Nuxt を初めて使って SSG することがあり少しハマったのでメモ書きです。

プロジェクトの構成など

version

{
  "dependencies": {
    "@nuxt/content": "^1.13.1",
    "nuxt": "^2.14.12",
  }
}

説明用に簡略化してますが、ディレクトリはこんな感じで、

.
└── src
    ├── content
    │   └── post
    │       ├── 1.json
    │       ├── 2.json
    │       └── 3.json
    └── pages
        ├── post
        |   └── _slug.vue
        └── index.vue

やってることは

ルート vue ファイル
/ src/pages/index.vue src/content/post/ 配下の json を nuxt/content を使って全て取得して一覧表示
/post/[slug] src/pages/post/_slug.vue src/content/post/[slug].json を nuxt/content を使って取得して詳細表示

という感じのオーソドックスな SSG のプロジェクトだと思います。

Nuxt の SSG における動的ルート

Nuxt の場合、 クローラーが統合されていて動的ルート(今回だと /post/[slug] )なども特に何もしなくても検出してルートを生成してくれます。 このあたりは Gatsby や Next で SSG する際には自分でルートを設定する必要があったので、楽できて良いなぁと思っていたわけです。

nuxt/content を使って json ファイルを読み込んで動的にページを作成していましたが、実際に動かしてみるとちゃんと自動クロールしてくれているようでした。

一部のページのルートが生成されていない?

ある程度開発が進み、お客さんからフォードバックをもらうタイミングになって、一部のページでURLに直接移動した場合やリロードした場合に 404 になることが発覚しました。

ルートが生成されてないんだろうなということはすぐに分かったのですが、なぜ一部のページだけが?と不思議に思いながら原因を探りました。

自動クロール出来てなかったことが原因

結論から書くと

  • 自動クロール出来てなかったページがあって、そのページのルートが生成されていなかった

ということでした。

もう少し詳しく説明すると、今回作っていたのは下記のような一覧ページと詳細ページがあるよくあるタイプのページ構成でした。

  • 一覧ページを開くと、9件のデータのリンクが表示される
  • 「もっと見る」ボタンを押下すると、10件目以降の全てのデータのリンクも表示される
  • データのリンクを押下すると詳細ページに遷移する

ルートが生成されてないページというのは、全て10件目以降のデータの詳細ページだということが分かりました。

この「もっと読む」ボタンを押下して残りのデータを表示する処理は JavaScript で State を変えて出し分けしているだけで、リンクを介してページ遷移しているわけでありません。

リンクを介して遷移する通常のページネーションなどであれば自動クロールしてくれるはずですが、今回みたいなボタンを押すと JavaScript が走って初めて表示されるようなリンクは自動クロールされないと思われるので注意が必要です。

例えば無限スクロールなども普通に実装すると最初に表示されていないリンクは検出されないはず。

対応

原因が特定できたので、2つの方向性で対応を考えました。

  1. 自動クロールされるように実装を変える
  2. 自分でルートを追加する

1 は自動クロールされるためだけに実装変えるのも気持ち悪いなと思い却下。 2 の方法で対応することにしました。

nuxt.config.js を編集してルートを追加

generate.routes プロパティを使って、自分でルートを追加することが出来ます。

nuxt.config.js

export default {
  generate: {
    dir: 'public',
    async routes() {
      const { $content } = require('@nuxt/content')
      const files = await $content('post')
        .only(['slug'])
        .fetch()
      return files.map(file => `/post/${file.slug}`)
    }
  },
}

シンプルにルートの文字列が入った配列を設定しているだけですね。

ちなみに generate.crawler プロパティに false を設定するとクローラー自体を無効化することも出来ます。

export default {
  generate: {
    crawler: false
  }
}

最後に

以上、Nuxt を使って SSG してちょっとハマったところでした。 自動クロールは便利だなぁと思いましたが、場合によってはクロールしてくれないことがあるので注意が必要ですね。

余談ですが、今回初めてちゃんと Vue と Nuxt を触ってみて、 React が出来れば Vue も割と何とかなるという実感が得られたので収穫でした。