Nuxt 3でtrailing slashありのURLを使うときの設定
Nuxt 3でtrailing slashありのURLを使うのに特別な設定は必要ありません。しかし、webサイトと違いwebアプリではtrailing slashなしで統一するのが一般的で、Nuxtもtrailing slashなしを前提に開発されています。その結果、たまに罠にハマってしまうことがあります。
大前提
同じページにtrailing slashありとなしの2つのURLがあるのは望ましくありません。「このページはtrailing slashなしであのページはtrailing slashあり」みたいなことをするとミスの原因になるので、基本的にはプロジェクト内で統一するべきです。
プリレンダリング(SSG)時の設定
デフォルトではSSG(nuxi generate
)する際にtrailing slashなしのルートがレンダリングされます。すると、useRoute
から参照したパスにtrailing slashが付いておらず、たとえば生成されたHTMLのog:url
やcanonical
タグにtrailing slashなしのURLが設定されてしまう恐れがあります[1]。
幸い、Nuxt 3ではSSGの対象ページ(ルート)を列挙する処理にフックを挟むことができます。これを利用し、フック内で対象URLの全てにtrailing slashをつけることで、この問題を解決することができます。
export default defineNuxtConfig({
// 略
hooks: {
'prerender:routes': (context) => {
for (const path of [...context.routes]) {
if (!path.endsWith('.html') && path !== '/') {
context.routes.delete(path)
context.routes.add(`${path}/`)
}
}
}
// 略
})
NuxtLink
defineNuxtLink
のtrailingSlash
オプションを使うことで、trailing slashを自動的につけるNuxtLinkのカスタムコンポーネントを定義することができます。
なお、Nuxt 2ではルータのレベルでtrailing slashを制御するrouter.trailingSlash
オプションが存在したようですが、Nuxt 3にはありません。後述するroute middlewareを使うと良さそうです。
route middleware
export default defineNuxtRouteMiddleware((to, from) => {
if (!to.path.endsWith('/')) {
// origin部分は使わないのでexample.comそのままでいい
const { pathname, search, hash } = new URL(to.fullPath, 'https://example.com')
return navigateTo(`${pathname}/${search}${hash}`)
}
})
ルータでのページ遷移に介入するミドルウェア[2]を利用して、trailing slashありのURLに強制的に遷移させることができます。ただし、SSG時にはリダイレクト(302)がエラー扱いになってしまうので、これだけで問題を解決することはできません。上述したフックの設定が必要です。
余談: Nuxt 3のSSGの挙動について
Nuxt 3のSSGは内部的にnitroによって実装されています。nitroがクローラとしてページの内容を取得し、それを.output/public
下にHTMLとして出力する仕組みです。さらに、元のページからリンクされたページもクローリング対象として追加されます。この挙動のおかげで、静的ルーティングのページからリンクをつたって到達できる範囲なら動的ルーティングのページもSSGされます。
ここで、たとえばpages/blog.vue
が存在する場合を考えます。このページには/blog
と/blog/
の両方でアクセスできてしまいますが、SSG時にはデフォルトで/blog
の方にアクセスされます。しかし、もし他のページから/blog/
に対してリンクが貼られていた場合、/blog/
もクローリングの対象となります。/blog/
と/blog
はあくまで異なるURLですが、SSG時の出力先はともに.output/public/blog/index.html
です。すると、先にクローリングされた方が後の方で上書きされてしまいます。
nitroのクローリングの順番は当然ながら実装依存です。もし深さ優先探索で、かつ/
から/blog/
へリンクされている場合、/blog/
が/blog
より先にクロールされます。一方、幅優先探索であれば/blog
の方が先です。私が試した限りでは、現時点では幅優先探索っぽい挙動でした。つまり、この場合/blog/
が/blog
を上書きします。結果的には意図通りtrailing slashありが生き残りましたが、安定性が見込めないnitro内部の実装・仕様に依存するべきではありません。
-
後述しますが、ならないこともあります。 ↩︎
-
ややこしいですが、server middlewareとは別です。 ↩︎
Discussion