Open15

Nuxt3 SSG 周りのメモ

SNKWSNKW

v3.12になってからprerender:routesに/しか渡されなくなり、この対処法が使えなくなりました。対処法が見つかっておらず、かなり困っています。

確認した限りだと、nitro.prerender.routesに手動で渡したパスや、prerender:routesより前に実行されるライフサイクルフック(nitro:config など)で追加したパスはコールバックの引数として狙った通りに参照することが可能なはず。

hookで動的なパスを設定する例

nuxt.config.ts
export default defineNuxtConfig({
  hooks: {
    "nitro:config": ( async config => {
      const posts = await fetch("https://jsonplaceholder.typicode.com/posts").then(res => res.json())
      config.prerender?.routes?.push(
        ...posts.map(c => `/posts/${c.id}`)
      )
    })
  }
})
SNKWSNKW

generate実行時のログを見ても、trailing slashなしのパスは出力されていないので、参考資料に記載されている方法は現在においても有効だとうかがえる

nuxt.config.ts
export default defineNuxtConfig({
  hooks: {
    "prerender:routes": (({ routes }) => {
      for ( const route of routes ){
        if( !route.endsWith("/") && route  !== "/" ){
          routes.delete(route)
          routes.add(`${route}/`)
        }
      }
    }),
    "nitro:config": ( async config => {
      const posts = await fetch("https://jsonplaceholder.typicode.com/posts").then(res => res.json())
      config.prerender?.routes?.push(
        ...posts.map(c => `/posts/${c.id}`)
      )
    })
  }
})
SNKWSNKW

クローリング周りの挙動

nitroのクローリングの順番は当然ながら実装依存です。もし深さ優先探索で、かつ/から/blog/へリンクされている場合、/blog/が/blogより先にクロールされます。一方、幅優先探索であれば/blogの方が先です。私が試した限りでは、現時点では幅優先探索っぽい挙動でした。つまり、この場合/blog/が/blogを上書きします。

計100ページ程度の量であれば静的ページの扱いは上記の通りだが、(dynamic routes含め) 1580ページを生成するPJでは逆に trailing slash ありのページが先にクロール・生成されていた。

なので閾値を境に探索方式が切り替わるようになっている可能性あり・・・?
(こればかりはNitroの内部実装を読まないとなので今は何とも)

対策

nitroの generate hook で trailing slash なしのパスをスキップさせることで実現できそう

nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    hooks: {
      "prerender:generate" (route) {
        if( !(/\.(json|html|js)$/.test(route.route) || route.route.endsWith("/")) ){
          route.skip = true
        }
      }
    }
  },
}

!/\.(json|html|js)$/.test(route.route) を入れないと各ページの payload.json までスキップされてしまい、表示が壊れてしまうので必ず条件式に含める必要あり。

SNKWSNKW

生成前のタイミングで、対象のルートを上書きすることもできるので、スキップの代わりにパスの末尾にスラッシュを足す処理を入れることで trailing slashを強制するという目的は達成できるはず

nuxt.config.ts
nitro: {
    hooks: {
      "prerender:generate" (route) {
        if( !(/\.(json|html|js)$/.test(route.route) || route.route.endsWith("/")) ){
          route.route += "/"
        }
      }
    }
  },
SNKWSNKW

ただ、この場合同じルートに対して2度レンダリングが実行されてしまう可能性があるので、Set とかに生成済みのルートを保持しておいて、すでに生成済みならスキップする処理を入れたほうがコードの実行時間的にはいいかも (CodeBuildとかの実行時間による課金の場合など)

nuxt.ts
const renderedRoutes = new Set<string>()

//中略
nitro: {
    hooks: {
      "prerender:generate" (route) {
        if( !(/\.(json|html|js)$/.test(route.route) || route.route.endsWith("/")) ){
          route.route += "/"
        }
        if( route.contentType?.includes("html") ){
          if( renderedRoutes.has(route.route) ){
            route.skip = true
          } else {
            renderedRoutes.add(route.route)
          }
        }
      }
    }
  },

計測した範囲では100ページ程度の生成であれば有意な差はみられないが、実行結果のルート数は処理を入れる前に比べて重複分は無視されてカウントされているのを確認

SNKWSNKW

NEXT

nitro のクローラーの実装を見る

SNKWSNKW

prerender 周りの nitro hooks

nitro の src/prerender/prerender.ts の中で呼び出されている hookをまとめていく

SNKWSNKW

prerender:generate

コールバック引数の型: PrerenderRoute

PrerenderRouteの型定義
https://github.com/nitrojs/nitro/blob/dcbf309091a85609192267ca01596f966143ca22/src/types/prerender.ts#L1-L10

メモ:各ページの静的生成のに呼び出される。ここで手続き的に設定した値は、そのあとのページ生成ステップで反映された状態で実行される。

https://github.com/nitrojs/nitro/blob/dcbf309091a85609192267ca01596f966143ca22/src/prerender/prerender.ts#L276-L277

SNKWSNKW

prerender:route

コールバック引数の型: PrerenderRoute

メモ:各ページの静的生成のに呼び出される。prerender:generateとは違い、ここで手続き的に値を設定してもページ生成には反映されない。(というのもその後に実行される処理はログの出力だけのため)

ページ生成がスキップ/エラーの場合
https://github.com/nitrojs/nitro/blob/dcbf309091a85609192267ca01596f966143ca22/src/prerender/prerender.ts#L288-L294

ページ生成が正常に実行された場合
https://github.com/nitrojs/nitro/blob/dcbf309091a85609192267ca01596f966143ca22/src/prerender/prerender.ts#L320-L321

SNKWSNKW

prerender:done

コールバック引数の型

{
  prerenderRoutes: PrerenderRoute[],
  failedRoutes: PrerenderRoute[]
}

メモ:すべてのルートのプリレンダリングが完了したタイミングで呼び出される。後続にプリレンダリングに関して何か処理が挟まれるというわけでもないので、ログのファイル出力くらいしか用途はなさそう。

https://github.com/nitrojs/nitro/blob/dcbf309091a85609192267ca01596f966143ca22/src/prerender/prerender.ts#L340-L343

SNKWSNKW

クローラーの挙動

crawlLinks オプションが有効な場合、ページ生成の最後のタイミングでそのページに存在するリンク先を抽出して生成対象に追加する処理を行っている

https://github.com/nitrojs/nitro/blob/dcbf309091a85609192267ca01596f966143ca22/src/prerender/prerender.ts#L305-L318

https://github.com/nitrojs/nitro/blob/dcbf309091a85609192267ca01596f966143ca22/src/prerender/prerender.ts#L333-L336
https://github.com/nitrojs/nitro/blob/dcbf309091a85609192267ca01596f966143ca22/src/utils/parallel.ts
ここでの実装を読む限り、生成対象のリストを先頭から順番に処理しているようなので参考資料で言及されている通り幅優先探索になているはず。