yourmystar tech blog
著者: nakane 公開日:

Vue Router 4 (Vue.js 3) で 404 ページを表示する

この記事はユアマイスター アドベントカレンダー 2022の 11 日目の記事です。

はじめに

Vue Router は Vue.js で SPA を構築するための公式ルータです。
この記事は、Vue Router 4 で 404 ページを表示する方法を紹介します。
(Vue Router 3 は Vue.js 2 に、Vue Router 4 は Vue.js 3 に対応しています)

※SPA で catch-all としての 404 ページを表示する方法であり、ステータスコード:404 としてページを返す方法ではないことをご了承ください

ルート定義

公式ドキュメントにも記載されていますが、path: '/:pathMatch(.*)*'をルートの最下部に定義します。
catch-all としてルートマッチし、404 ページを表示します。
pathMatchは変数名であるため、任意の名前で問題ありません

// src/router/index.ts

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '@/views/HomeView.vue'
// ... 省略
import NotFoundView from '@/views/NotFoundView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: HomeView,
    },
    // ... 省略
    // 404 最下部に定義
    {
      path: '/:pathMatch(.*)*',
      name: 'not-found',
      component: NotFoundView,
    },
  ],
})

export default router

ルート定義の深堀り

path: '/:pathMatch(.*)*'は一見(冗長な)正規表現のように見えますが、Vue Router が提供する以下のルートマッチシンタックスを組み合わせています。

Custom regex in params

正規表現により柔軟なルート定義を可能にします。
例えば、(設計の良し悪しはさておき、)パラメータが 数字 or 文字列 でルートを分けたい場合、以下のように定義することができます。

const routes = [
  // パラメータが数字にマッチ
  // 例. department/1, department/99
  {
    path: 'department/:departmentId([1-9][0-9])',
    name: 'department-id',
    component: DepartmentIdView,
  },
  // パラメータが1文字以上の半角英字にマッチ
  // 例. department/sales, department/accounting
  {
    path: 'department/:departmentName([a-z]+)',
    name: 'department-name',
    component: DepartmentNameView,
  },
]

Repeatable params

*+は、ディレクトリ階層を持つルート (例. aaa/bbb/ccc/ddd) にマッチするシンタックスです。
例えば、 都道府県、都道府県/市区町村、都道府県/市区町村/町名 ...など住所の階層を持つルートをまとめて以下のように定義することができます。

const routes = [
  // 都道府県、都道府県/市区町村、都道府県/市区町村/町名、 ...にマッチ
  // 例. area/, area/tokyo, area/tokyo/setagaya, area/tokyo/setagaya/mishuku ...
  {
    path: 'area/:area*',
    name: 'area',
    component: AreaView,
  },
]

Custom regex in params と Repeatable params の組み合わせ

Custom regex in params と Repeatable params を組み合わせることができます。(公式ドキュメント)
path: '/:pathMatch(.*)*'.**の組み合わせで、
任意の文字列から成る任意のディレクトリの階層、つまりすべてのパスにマッチするルート定義です。

任意のパスを 404 ページとして表示する

パスを指定して 404 ページを呼び出すことができます。
pathMatchキーの値にパスパラメータを配列(ディレクトリ階層がない場合は文字列も可)で指定します。

// パス:/aaa/bbb/ccc/ddd を404ページとして表示する
{ name: 'not-found', params: { pathMatch: ['aaa', 'bbb', 'ccc', 'ddd'] } }

例えば、「タグ一覧ページからタグ別ポスト一覧ページを開くとき、ポストが存在しない場合は空ページの代わりに 404 ページを表示する」というユースケースを以下のように書くことができます。

// src/router/index.ts

import { createRouter, createWebHistory } from 'vue-router'
import { provideApolloClient } from '@vue/apollo-composable'
import apolloClient from '@/plugins/apolloClient'
import TagsView from '@/views/TagsView.vue'
import ListByTagView from '@/views/ListByTagView.vue'
// ... 省略
import NotFoundView from '@/views/NotFoundView.vue'

provideApolloClient(apolloClient)

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/tags',
      name: 'tags',
      component: TagsView,
    },
    {
      path: '/tags/:tagSlug',
      name: 'list-by-tag',
      component: ListByTagView,
      props: true,
      beforeEnter: async (to, from, next) => {
        try {
          const tagSlug = to.params.tagSlug
          const {
            data: { posts },
          } = await apolloClient.query({
            // ... 省略
          })
          if (posts.length) {
            next()
            return
          }
          next({
            name: 'not-found',
            params: { pathMatch: ['tags', `${tagSlug}`] },
          })
          return
        } catch (e) {
          console.error(e)
        }
      },
    },
    // ... 省略
    // 404 最下部に定義
    {
      path: '/:pathMatch(.*)*',
      name: 'not-found',
      component: NotFoundView,
    },
  ],
})

export default router

終わりに

Vue Router 4 で 404 ページを表示する方法を紹介しました。どなたかのお役に立てたら嬉しく思います。

ツイートするはてなブックマークに追加シェアする