Vue Routerの動的ルーティングでAPIが404の場合のNot Found 表示について

タイトル通りな小ネタです。
Vue Router とは書いてあるものの SPA のようにフロントエンドでルーティングを行い、API を利用しているパターンではフレームワーク問わず再利用可能なネタだとは思いますので、誰かのお役に立てばと思います。

サンプルコードでよく紹介されているVue Routerの404

動的ルートマッチング | Vue Router

const router = new VueRouter({
  routes: [
    { path: '/user/:id(\d+)', component: User, props: (route) => ({ userId: route.params.userId }) },
    { path: '/*', component: NotFound }
  ]
})

ググると紹介されているよくある not found パターンのコードですね。

path のどれにもにマッチングしない場合は /* にマッチングし、 NotFound コンポーネントが表示されます。

しかし、 /user/:id のようにパターンにマッチングはするが、コンポーネントからAPIを呼び出したものの params で渡ってきた id がバックエンド側で404だった場合には対応できません。

そのため、APIが404だった場合にコンポーネント側のコードで対応する必要があります。

対応方法としては私は2パターンあると思います。

Redirectを行う

APIのレスポンスを見て、404だった場合に /not-found のような存在しないURLにリダイレクトさせて not foundコンポーネントを表示させる方法です。

async fetchUser() {
  const res = await axios.get(`/user?ID=${userID}`)
  // 存在しないユーザーは 404 となるのでリダイレクト
  if (res.status === 404) {
    await this.$router.replace({
      path: '/not-found',
    })
  }
}

メリット

アプリケーションに後付けできる手軽さがあります。

コンポーネントも追加も行わず、APIの共有処理に追加すれば全体で使うこともできます。

デメリット

URLが変わってしまうことです。 /not-found などルーティングにマッチしない適当なURLにリダイレクトしているため元いたページとは別のURLになります。

ページもしくはApp全体をv-ifで覆う

// App.vue
<template>
  <div class="App">
    <NotFound v-if="isNotFound" />
    <router-view v-else />
  </div>
</template>

このようにApp全体の表示を切り替えられる領域を v-if を使い切り替える方法です。

isNotFound の変数は Vuex などを利用し、子コンポーネントから切り替えられる実装にしておくと良さげです。

メリット

先ほどのリダイレクトではURLが変わってしまいましたが、この方法ではルーティングマッチングしたコンポーネントで v-if を使った表示の切り替えを行うためURLをそのままNot Found表示ができます。

デメリット

コンポーネントを追加する作業があるので、後付けでは少々面倒かもです。とはいえ、URLがそのままなのはよくあるWebサイトの動作なのでユーザには自然に感じるかもしれません。

まとめ

アプリケーション初期構築時に API 利用がわかっているなら後者のコンポーネント切り替えで実装したいとは個人的に思ってます。
が、後から追加になったりすると前者のリダイレクトで改修範囲を最小限にしたりと、時々の使い分けになるかなと思ってます。
こういう要件はアプリケーションの初期構築時に考慮できているといいですね。