🥳

Nuxt.js 3で動的ルーティングのFull static generationをする

2023/01/12に公開

Nuxt.js 3.6.5で動的なルーティングでFull static generationをするアプローチを検証したので、備忘録として残します。

Full static generationとは

Nuxt.jsには、2.13から静的生成機能(Static Site Generation=SSG)が実装されており、ビルド時にページを生成しておくことができ、クライアントサイドのレンダリングコストを低減できます。SSGの説明はこちらの記事がとても参考になりました。

https://zenn.dev/rinda_1994/articles/e6d8e3150b312d#ssg

Full static generationはページ遷移時に呼ばれるAPIのレスポンスなども含め、全てのページを完全に静的化する機能です。

昨年秋にリリースされた待望のNuxt.js 3.0.0でもFull static generationは利用できるようですが、Payload Extractionはまだ実験的な機能のようです。
https://github.com/nuxt/framework/issues/6411

また、動的なルーティングがある場合にどうやってFull static generationするか、公式ドキュメントに記載がありませんでした。また、2.xとも挙動が異なるようで、3.0.0でもFull static generationできないか試してみた備忘録を残しておきます。

完全静的化をやってみる

リポジトリを作ったのでこちらを使いながらやってみます。
https://github.com/konkarin/nuxt3-ssg-dynamic-routing

動的ルーティングのページをSSGする

Nuxt.js 2.13 では公式ドキュメントにある以下の方法を使って、動的ルーティングがあってもページごとに完全静的化できました。

https://nuxtjs.org/docs/configuration-glossary/configuration-generate/#speeding-up-dynamic-route-generation-with-payload

// nuxt.config.js
import axios from 'axios'

export default {
  generate: {
    routes() {
      return axios.get('https://my-api/users').then(res => {
        return res.data.map(user => {
          return {
            route: '/users/' + user.id,
	    // payloadの指定でAPIレスポンスが各ページごとのpayload.jsに保存される
            payload: user
          }
        })
      })
    }
  }
}

APIレスポンスを元にroutepayloadを指定でき、routeに指定されたパスに応じたページコンポーネント内でpayloadasyncData()の引数として受け取ることができます。

これは 3.x からは利用できなくなっているようで、別のアプローチがissueに記載されていました。
https://github.com/nuxt/nuxt/issues/13949#issuecomment-1397322945

3.x の動的ルーティングの静的生成はこのような書き方になります。

// nuxt.config.ts
export default defineNuxtConfig({
  hooks: {
    async "nitro:config"(nitroConfig) {
      if (nitroConfig.dev) {
        return;
      }
      const res = await axios.get("https://api.nuxtjs.dev/mountains");
      if (nitroConfig.prerender?.routes === undefined) {
        return;
      }
      nitroConfig.prerender.routes = res.data.map((mount: any) => {
        return `/mountains/${mount.slug}`;
      });
    },
  },
});

ただし、こちらは nitro の config 経由で、動的ルーティングの静的生成のみ実行します。これだけでは2.13のように、ページごとにAPIレスポンスまで含めた完全静的化は実現できません😭。

動的ルーティングのページ内のAPIレスポンスを静的化する

動的ルーティングのページコンポーネント内でuseFetch()するだけでOKです。useFetch()で取得したAPIレスポンスが_payload.jsonとして各ページの分だけ生成されます。

const { data: mountains } = await useFetch<any>(
  "https://api.nuxtjs.dev/mountains",
  {
    key: "mountains",
  }
);

とはいえ、2.13 とは異なり、生成されるページの数だけuseFetch()でAPIリクエストされるので、ページ数が多いとビルド時間がかかりそうでちょっと不便です😢

ここは迂回策があれば是非利用したいところです。

いざ生成

では実際に静的生成してみましょう。
nuxt generateで生成します。generate 時はこのような警告が出ますが、恐れずこのまま進みましょう。

WARN  Using experimental payload extraction for full-static output. You can opt-out by setting experimental.payloadExtraction to false.

完了すると、.output配下にファイル群が生成されます。ディレクトリを見てみると、mountains配下に動的ルーティングのページが生成され、なおかつページごとに_payload.jsonとしてAPIのレスポンスデータも保存されていることがわかります。

HTMLについても同様に、APIレスポンスの結果も含めてレンダリングされていることがわかります。

nuxt previewでブラウザでも動作確認してみましょう。
ご覧の通り、APIリクエストが飛ばずにページがブラウザ上で表示できていることがわかります。

これでめでたく動的ルーティングでもFull static generationが実現できました🥳

おわりに

本記事を書くにあたって、かなりNuxt.jsのGitHubリポジトリを読み漁りました。今回のように実験的な機能では、Googleで調べるよりも直接GitHubを見に行ってPRやissueを見たほうが近道になることもありますね。また、メンテナの方々の素晴らしいコードもたくさん読めるのでとても勉強になりました。
Stable版がリリースされたとはいえ、まだまだ実験的な機能も多いようなので、今後のNuxt.jsの盛り上がりが楽しみです!

Discussion