trailingSlash 付きの URL で統一する

Nuxt.js
公開日:

Nuxt.js の router.trailingSlash プロパティを true に設定した場合の備忘録です。

トレイリングスラッシュとは

URL の末尾につくスラッシュ(/)のことです。

ドメインのあとのトレイリングスラッシュについては、「なし」でアクセスすると、実際には「あり」の URL に自動で変わります。

'https://example.com'  // なし⇒「あり」に変わる
'https://example.com/' // あり

サブディレクトリに於いては、URL の解釈が異なります。

'https://example.com/foo'  // example.com の foo ファイルを要求する
'https://example.com/foo/' // example.com の foo ディレクトリを要求する

「なし」では foo ファイル(拡張子なし)を要求していますが、大抵のサーバーでは、そのファイルがなければ同名のディレクトリへのアクセスと見なして、foo ディレクトリの index.html を提供します。「あり」にリダイレクトするサーバーもあるでしょう。

「あり」では、単に foo フォルダにある index.html が提供されます。

Jamstack は予めすべてのページの index.html を作っておくというのが肝なので、こちらに統一すれば良いでしょう。

統一するメリットとしては、検索エンジンが重複するコンテンツとして「あり」「なし」双方のページをインデックスしてしまうのを避けることができます。

Nuxt.js でトレイリングスラッシュを強制する

Nuxt.js では router.trailingSlash プロパティを true にすることで、「なし」の URL が機能しなくなります。

// nuxt.config.js
export default {
  router: {
    trailingSlash: true,
  },
}

この状態で例えば pages/foo/index.vue ファイルがあったとしても、トレイリングスラッシュなしでの遷移では 404 になります。

<nuxt-link to="/foo">なし</nuxt-link> <!-- 404 エラーページが表示される -->
<nuxt-link to="/foo/">あり</nuxt-link><!-- /foo/ に遷移して、pages/foo/index.vue の内容が表示される -->

このようにどこか一つでも「なし」のリンクを書いてしまうと、クリックされたらエラーページが表示されてしまいます。

これを防ぐため、ルートが切り替わる度に実行されるルーターミドルウェアを追加します。

ミドルウェアに渡されるコンテキストの route には遷移先のルートオブジェクトが入ってくるので、route.path がトレイリングスラッシュで終わってなければ追加します。

// middleware/addTrailingSlash.js
export default function({ route, redirect }) {
  if (route.path.slice(-1) !== '/') {
    route.path += '/'
    return redirect(route) // ルートオブジェクトを渡すことでパラメータやハッシュを残せる
  }
}

nuxt.config.js の router.middleware プロパティに追加します。router.trailingSlashtrue じゃないと、ミドルウェアが「なし」を「あり」に書き換え→トレイリングスラッシュは付けない設定なので「なし」に戻される→ミドルウェアが書き換え……という無限ループに陥ります。

// nuxt.config.js
export default {
  router: {
    middleware: ['addTrailingSlash'],
    trailingSlash: true,
  },
}

router.extendRoutes で追加したルートに名前で遷移すると無限ループ

extendRoutes で増やしたルートにルート名で遷移すると、先のミドルウェアとのコンボで無限ループに陥ります。

<nuxt-link to="/categories/nuxtjs/">普通に遷移できる</nuxt-link>
<nuxt-link :to="{ name: 'index-categories-categId', params: { categId: 'nuxtjs' }}">無限ループ</nuxt-link>

試しに router.extendRoutes で Nuxt.js が自動で生成したルートをログに出力してみると、ルートごとに pathToRegexpOptions というプロパティが増えていました。

これに倣って extendRoutes を書き直します。以下は pages/index.vue を親として、pages/index/ 以下にすべてのページを作っている場合の設定です。

// nuxt.config.js
import { sortRoutes } from '@nuxt/utils'
export default {
  router: {
    middleware: ['addTrailingSlash'],
    trailingSlash: true,
    extendRoutes(routes, resolve) {
      const pathToRegexpOptions = { stricttrue } // これがルートごとに必要
      const { children } = routes.find(route => route.path === '/')

      children.push(
        {
          name'index-page-page',
          path'page/:page/', // トレイリングスラッシュを忘れずに付ける
          componentresolve(__dirname, 'pages/index/index'),
          pathToRegexpOptions,
        },
        {
          name'index-categories-categId',
          path'categories/:categId/', // トレイリングスラッシュを確実に付ける
          componentresolve(__dirname, 'pages/index/index'),
          pathToRegexpOptions,
        },
        {
          name'index-categories-categId-page-page',
          path'categories/:categId/page/:page/', // トレイリングスラッシュを必ず付ける
          componentresolve(__dirname, 'pages/index/index'),
          pathToRegexpOptions,
        },
      )
      // ルートの並べ替え
      sortRoutes(routes)
    },
  },
}

ルート名での遷移について

router.trailingSlashtrue だと、ルート名を指定しての遷移でトレイリングスラッシュが付くようになります。

// !router.trailingSlash 
this.$router.push({ name: 'index-search', query: { q: 'foo' }}) // /search?q=foo に遷移

// router.trailingSlash === true
this.$router.push({ name: 'index-search', query: { q: 'foo' }}) // /search/?q=foo に遷移

パスでの遷移と比べると、Vue Router が URL 解析をしなくて済むという利点があります。

パスの文字列を分解してクエリ部分を parseQuery() でオブジェクトにするといった処理が実行されなくなるので、多少は軽くなるでしょう。

ただ記述量は増えてしまいますし、あまり直感的でもないので、好きな方で記述すればよいかと思います。

<!-- パス文字列の解析が必要。トレイリングスラッシュを忘れてはならない -->
<nuxt-link :to="`/${post.id}/`">string path</nuxt-link>

<!-- query がないのは明白なので parseQuery() は呼ばれない -->
<nuxt-link :to="{ name: 'index-postId', params: { postId: post.id }}">named route</nuxt-link>

Vercel でトレイリングスラッシュを強制する

Vercel では、ルートとして設定したディレクトリに vercel.json を置くことで、サーバーの挙動を変更できます。

trailingSlash プロパティに真偽値を設定すると、トレイリングスラッシュの有無を統一できます。

// vercel.json
{
  "trailingSlash": true
}

true に設定した場合、「なし」のリクエストを「あり」の URL に 308 リダイレクトします。

/favicon.svg など、ファイルへのアクセスはリダイレクトされないので、要求されたファイルがなければトレイリングスラッシュを付けた URL にリダイレクトするという風になっているようです。

また true の場合、自作 API も「あり」のエンドポイントに変更されます。

serverMiddleware で API をシミュレートしている場合は、そちらも「あり」のエンドポイントに修正しましょう。

// nuxt.config.js
export default {
  serverMiddleware: [
    { path: '/api/search/', handler: '~/api/search.js' },
  ],
}