T-CREATOR

Vue.js Router 速見表:ガード・遅延ロード・トランジションの定石

Vue.js Router 速見表:ガード・遅延ロード・トランジションの定石

Vue.js Router を使いこなすには、ナビゲーションガード、遅延ロード、トランジションの 3 つの機能を理解することが重要です。この記事では、実務で即使える定石パターンを速見表とともにご紹介します。

初めて Vue Router を触る方も、すでに使っている方も、この記事を読めば明日から実践できるテクニックが身につくでしょう。

速見表

ナビゲーションガード一覧

#ガード種類実行タイミング用途定義場所
1beforeEach全ルート遷移前認証チェック、ログ記録ルーターインスタンス
2beforeResolveナビゲーション確定直前データ取得完了待ちルーターインスタンス
3afterEachナビゲーション完了後アナリティクス送信ルーターインスタンス
4beforeEnter特定ルート遷移前ルート固有の認証ルート定義
5beforeRouteEnterコンポーネント遷移前データプリロードコンポーネント内
6beforeRouteUpdate同一コンポーネント更新時パラメータ変更対応コンポーネント内
7beforeRouteLeaveコンポーネント離脱前未保存確認コンポーネント内

遅延ロード実装パターン

#パターン構文メリット推奨用途
1基本的な動的 import() => import('.​/​Component.vue')シンプル小規模アプリ
2webpack チャンク名指定() => import(​/​* webpackChunkName: "group" *​/​ '.​/​Component.vue')グループ化可能中〜大規模アプリ
3Prefetch 指定() => import(​/​* webpackPrefetch: true *​/​ '.​/​Component.vue')事前読み込み優先度高いページ
4Preload 指定() => import(​/​* webpackPreload: true *​/​ '.​/​Component.vue')並列読み込み初期表示に必要

トランジション設定一覧

#設定項目効果使用例
1name文字列トランジション名"fade", "slide"
2mode"out-in"退出後に入場ページ切り替え
3mode"in-out"入場後に退出オーバーレイ
4カスタムクラスenter-active-class独自アニメーションAnimate.css 使用時
5JavaScript Hook@before-enter など複雑な制御GSAP 使用時

背景

SPA におけるルーティングの重要性

Vue.js を使った SPA(Single Page Application)開発では、ルーティングがアプリケーションの核となります。

従来のマルチページアプリケーションと異なり、SPA ではページ遷移時にサーバーへのリクエストが発生しません。すべてのページ遷移は JavaScript 側で制御されるため、ユーザー体験が大きく向上するのです。

しかし、この柔軟性は同時に複雑さも生み出します。ページ遷移前の認証チェック、データの事前読み込み、スムーズなページ切り替えアニメーションなど、考慮すべき点が多岐にわたるでしょう。

Vue Router の 3 つの柱

Vue Router は、これらの課題に対して 3 つの強力な機能を提供しています。

ナビゲーションガードは、ページ遷移の各タイミングでフックを実行し、遷移の制御や前処理を可能にします。遅延ロードは、必要なコンポーネントだけを動的に読み込み、初期読み込み時間を短縮するでしょう。トランジションは、ページ切り替え時のアニメーションを簡単に実装できますね。

次の図は、これら 3 つの機能がルーティングのライフサイクルのどこで働くかを示しています。

mermaidflowchart TD
    start["ルート遷移開始"] --> guard1["グローバル<br/>beforeEach"]
    guard1 --> guard2["ルート固有<br/>beforeEnter"]
    guard2 --> guard3["コンポーネント<br/>beforeRouteEnter"]
    guard3 --> lazy["遅延ロード<br/>コンポーネント取得"]
    lazy --> guard4["グローバル<br/>beforeResolve"]
    guard4 --> transition_out["トランジション<br/>退出アニメーション"]
    transition_out --> guard5["グローバル<br/>afterEach"]
    guard5 --> transition_in["トランジション<br/>入場アニメーション"]
    transition_in --> done["ナビゲーション完了"]

この図から、ナビゲーションガードが遷移の各段階で実行され、遅延ロードがコンポーネント取得時に、トランジションが最終段階で動作することがわかります。

課題

アクセス制御の難しさ

SPA では、すべてのルートが最初からブラウザに読み込まれているため、URL を直接入力すればどのページにもアクセスできてしまいます。

認証が必要なページへの不正アクセスを防ぐには、ページ遷移前にユーザーの権限をチェックする仕組みが必要です。各コンポーネントで個別に認証チェックを実装すると、コードの重複が発生し、メンテナンスが困難になるでしょう。

また、権限チェックのタイミングも重要です。コンポーネントがマウントされてからチェックするのでは遅く、一瞬でも未認証ユーザーにコンテンツが表示されてしまう可能性があります。

パフォーマンスのボトルネック

Vue.js アプリケーションが大規模化すると、すべてのコンポーネントを最初に読み込むと初期読み込み時間が長くなってしまいます。

特にダッシュボードや管理画面など、多数の画面を持つアプリケーションでは、ユーザーがアクセスしない画面まで最初に読み込むのは無駄ですよね。バンドルサイズが大きくなると、モバイル環境でのユーザー体験が著しく低下するでしょう。

Webpack などのバンドラーは最適化機能を持っていますが、開発者が適切にコード分割を指示しなければ、その恩恵を受けられません。

ユーザー体験の向上

ページ遷移が瞬時に行われる SPA では、ユーザーが今どのページにいるのか分かりにくくなることがあります。

突然コンテンツが切り替わると、ユーザーは違和感を覚えるかもしれません。適切なトランジションアニメーションを加えることで、ページ遷移を自然に感じさせ、アプリケーション全体の品質を高められます。

しかし、アニメーションの実装は意外と複雑で、CSS アニメーションと JavaScript の制御を適切に組み合わせる必要があるでしょう。

次の図は、これらの課題がどのように関連しているかを示しています。

mermaidflowchart LR
    spa["SPA開発"] --> issue1["アクセス制御<br/>の難しさ"]
    spa --> issue2["パフォーマンス<br/>ボトルネック"]
    spa --> issue3["UX向上<br/>の必要性"]

    issue1 --> prob1["不正アクセス<br/>リスク"]
    issue1 --> prob2["コード重複"]

    issue2 --> prob3["初期読み込み<br/>遅延"]
    issue2 --> prob4["バンドル<br/>肥大化"]

    issue3 --> prob5["遷移の<br/>違和感"]
    issue3 --> prob6["実装の<br/>複雑さ"]

解決策

ナビゲーションガードで安全な遷移制御

ナビゲーションガードは、ルート遷移のライフサイクル各段階でフックを提供し、遷移の制御や前処理を可能にします。

7 種類のガードが用意されており、それぞれ実行タイミングと用途が異なります。グローバルガードはすべての遷移で実行され、ルート固有ガードは特定のルートでのみ、コンポーネント内ガードはコンポーネントレベルで実行されるのです。

この階層的な設計により、認証チェックはグローバルで、特定ページの権限チェックはルート固有で、といった使い分けができるでしょう。

遅延ロードで最適なパフォーマンス

遅延ロード(Lazy Loading)は、ルートコンポーネントを動的にインポートすることで、必要なときだけコードを読み込む技術です。

JavaScript の動的import()構文を使用し、Webpack などのバンドラーが自動的にコード分割を行います。これにより、初期バンドルサイズを大幅に削減し、アプリケーションの起動時間を短縮できますね。

さらに、Webpack のマジックコメントを使えば、関連するコンポーネントをグループ化したり、事前読み込みを指示したりできるでしょう。

トランジションで洗練された UX

Vue Router のトランジション機能は、Vue の<transition>コンポーネントと統合されており、ページ切り替え時のアニメーションを簡単に実装できます。

CSS トランジション、CSS アニメーション、JavaScript フックのすべてをサポートし、フェード、スライド、ズームなど多様なエフェクトを実現可能です。mode属性により、退出と入場のタイミングを制御できますね。

次の図は、3 つの解決策がどのように課題に対応するかを示しています。

mermaidflowchart LR
    guard["ナビゲーション<br/>ガード"] --> solve1["認証・権限<br/>チェック"]
    guard --> solve2["遷移制御"]
    guard --> solve3["データ<br/>プリロード"]

    lazy["遅延ロード"] --> solve4["バンドル<br/>分割"]
    lazy --> solve5["初期読み込み<br/>高速化"]
    lazy --> solve6["メモリ<br/>効率化"]

    transition["トランジション"] --> solve7["スムーズな<br/>切り替え"]
    transition --> solve8["視覚的<br/>フィードバック"]
    transition --> solve9["ブランド<br/>体験向上"]

具体例

ナビゲーションガードの実装

それでは、実際にナビゲーションガードを実装していきましょう。まずはルーターの基本設定から始めます。

ルーター基本設定

ルーターインスタンスを作成し、グローバルガードを設定します。

typescript// router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import type { RouteRecordRaw } from 'vue-router';

次に、ルート定義を作成します。メタ情報に認証要否を記載することで、ガードで判断できるようにします。

typescript// ルート定義
const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/Home.vue'),
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('../views/Login.vue'),
  },
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: () => import('../views/Dashboard.vue'),
    meta: { requiresAuth: true }, // 認証が必要
  },
];

ルーターインスタンスを作成します。

typescript// ルーターインスタンス作成
const router = createRouter({
  history: createWebHistory(),
  routes,
});

グローバル beforeEach ガード

最も頻繁に使用されるグローバルガードです。すべてのナビゲーション前に実行され、認証チェックに最適でしょう。

typescript// グローバルbeforeEachガード - 認証チェック
router.beforeEach((to, from, next) => {
  // 認証が必要なページかチェック
  const requiresAuth = to.matched.some(
    (record) => record.meta.requiresAuth
  );

  // ログイン状態を確認(実際はストアやAPIから取得)
  const isAuthenticated =
    localStorage.getItem('token') !== null;

  if (requiresAuth && !isAuthenticated) {
    // 未認証の場合、ログインページへリダイレクト
    next({
      name: 'Login',
      query: { redirect: to.fullPath },
    });
  } else {
    // 認証済み、または認証不要の場合は続行
    next();
  }
});

このコードでは、to.matched.some()で親ルートも含めて認証要否をチェックしています。next()の引数により、遷移の許可、リダイレクト、キャンセルを制御できますね。

グローバル beforeResolve ガード

すべての非同期コンポーネントが解決された後、ナビゲーション確定直前に実行されます。

typescript// グローバルbeforeResolveガード - データ取得完了確認
router.beforeResolve(async (to, from, next) => {
  // ページタイトルを設定
  if (to.meta.title) {
    document.title = `${to.meta.title} | My App`;
  }

  next();
});

グローバル afterEach ガード

ナビゲーション完了後に実行されます。遷移を中断できないため、アナリティクスやログ記録に使用します。

typescript// グローバルafterEachガード - アナリティクス送信
router.afterEach((to, from) => {
  // Google Analyticsへページビュー送信
  if (window.gtag) {
    window.gtag('config', 'GA_MEASUREMENT_ID', {
      page_path: to.fullPath,
    });
  }

  // ページ遷移のログ記録
  console.log(
    `ページ遷移: ${from.fullPath} -> ${to.fullPath}`
  );
});

ルート固有 beforeEnter ガード

特定のルートにのみ適用されるガードです。管理者ページなど、特別な権限チェックが必要な場合に使用します。

typescript// ルート定義にbeforeEnterを追加
const adminRoute: RouteRecordRaw = {
  path: '/admin',
  name: 'Admin',
  component: () => import('../views/Admin.vue'),
  beforeEnter: (to, from, next) => {
    // 管理者権限をチェック
    const userRole = localStorage.getItem('userRole');

    if (userRole === 'admin') {
      next();
    } else {
      // 権限がない場合はホームへリダイレクト
      next({ name: 'Home' });
      alert('管理者権限が必要です');
    }
  },
};

コンポーネント内ガード

コンポーネント内で定義するガードです。<script setup>ではonBeforeRouteEnterなどのヘルパーを使用します。

typescript// views/Profile.vue
<script setup lang="ts">
import { onBeforeRouteEnter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'
import { ref } from 'vue'

const userData = ref(null)
const hasUnsavedChanges = ref(false)

// コンポーネント遷移前 - データプリロード
onBeforeRouteEnter((to, from, next) => {
  // この時点ではコンポーネントインスタンスにアクセス不可
  fetchUserData(to.params.id).then(() => {
    next()
  })
})

// 同一コンポーネント内でパラメータ変更時
onBeforeRouteUpdate(async (to, from) => {
  // 新しいユーザーIDでデータ再取得
  await fetchUserData(to.params.id)
})

// コンポーネント離脱前 - 未保存確認
onBeforeRouteLeave((to, from) => {
  if (hasUnsavedChanges.value) {
    const answer = window.confirm('変更が保存されていません。本当に離れますか?')
    return answer // falseを返すと遷移キャンセル
  }
})

async function fetchUserData(id: string) {
  // APIからユーザーデータ取得
  const response = await fetch(`/api/users/${id}`)
  userData.value = await response.json()
}
</script>

このコンポーネント内ガードの使い分けにより、データの事前読み込みや未保存変更の確認を適切なタイミングで実行できます。

次の図は、ナビゲーションガードの実行順序を示しています。

mermaidsequenceDiagram
    participant User as ユーザー
    participant Router as ルーター
    participant Guard as ガード群
    participant Component as コンポーネント

    User->>Router: ページ遷移要求
    Router->>Guard: beforeEach実行
    Guard->>Guard: 認証チェック
    Guard->>Router: 遷移許可/拒否
    Router->>Guard: beforeEnter実行
    Guard->>Guard: ルート固有チェック
    Router->>Component: beforeRouteEnter実行
    Component->>Component: データプリロード
    Router->>Guard: beforeResolve実行
    Router->>Guard: afterEach実行
    Guard->>User: ページ表示完了

遅延ロードの実装

遅延ロードを実装することで、アプリケーションの初期読み込み時間を大幅に短縮できます。

基本的な遅延ロード

最もシンプルな遅延ロードの実装です。矢印関数と dynamic import を使用します。

typescript// router/index.ts
const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    // 同期的にインポート(推奨しない)
    // component: Home

    // 遅延ロード(推奨)
    component: () => import('../views/Home.vue'),
  },
];

この構文により、Webpack は自動的にコンポーネントを別ファイルに分割し、必要なときだけ読み込みます。

Webpack チャンク名の指定

関連するコンポーネントを同じチャンクにグループ化することで、効率的に読み込めます。

typescript// 関連ページをグループ化
const routes: RouteRecordRaw[] = [
  {
    path: '/user/profile',
    name: 'UserProfile',
    component: () =>
      import(
        /* webpackChunkName: "user" */
        '../views/user/Profile.vue'
      ),
  },
  {
    path: '/user/settings',
    name: 'UserSettings',
    component: () =>
      import(
        /* webpackChunkName: "user" */
        '../views/user/Settings.vue'
      ),
  },
  {
    path: '/admin/dashboard',
    name: 'AdminDashboard',
    component: () =>
      import(
        /* webpackChunkName: "admin" */
        '../views/admin/Dashboard.vue'
      ),
  },
];

この例では、ユーザー関連ページはuser.jsに、管理者ページはadmin.jsに分割されます。同じチャンク名を指定したコンポーネントは 1 つのファイルにバンドルされるのです。

Prefetch と Preload の活用

Webpack の特殊なコメントを使用して、事前読み込みを制御できます。

typescript// Prefetch - ブラウザのアイドル時に事前読み込み
const routes: RouteRecordRaw[] = [
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: () =>
      import(
        /* webpackChunkName: "dashboard" */
        /* webpackPrefetch: true */
        '../views/Dashboard.vue'
      ),
  },
];

Prefetch は、ブラウザがアイドル状態のときにリソースを事前読み込みします。ユーザーがアクセスする可能性が高いページに使用しましょう。

typescript// Preload - 親チャンクと並列で読み込み
const routes: RouteRecordRaw[] = [
  {
    path: '/critical',
    name: 'Critical',
    component: () =>
      import(
        /* webpackChunkName: "critical" */
        /* webpackPreload: true */
        '../views/Critical.vue'
      ),
  },
];

Preload は親チャンクと並列で読み込まれ、優先度が高くなります。初期表示に必要なコンポーネントに使用します。

ネストされたルートの遅延ロード

親子関係のあるルートでも、それぞれ独立して遅延ロードできます。

typescriptconst routes: RouteRecordRaw[] = [
  {
    path: '/products',
    component: () => import('../layouts/ProductLayout.vue'),
    children: [
      {
        path: '',
        name: 'ProductList',
        component: () =>
          import(
            /* webpackChunkName: "products" */
            '../views/products/List.vue'
          ),
      },
      {
        path: ':id',
        name: 'ProductDetail',
        component: () =>
          import(
            /* webpackChunkName: "products" */
            '../views/products/Detail.vue'
          ),
      },
      {
        path: ':id/edit',
        name: 'ProductEdit',
        component: () =>
          import(
            /* webpackChunkName: "products-edit" */
            '../views/products/Edit.vue'
          ),
      },
    ],
  },
];

親レイアウトとリスト・詳細は同じチャンクに、編集ページは別チャンクにすることで、適切に分割できますね。

エラーハンドリング

遅延ロードの失敗に備えて、エラーハンドリングを実装しましょう。

typescript// エラーハンドリング付き遅延ロード
const routes: RouteRecordRaw[] = [
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: () =>
      import('../views/Dashboard.vue').catch((err) => {
        console.error(
          'Failed to load Dashboard component:',
          err
        );
        // フォールバックコンポーネントを返す
        return import('../views/ErrorPage.vue');
      }),
  },
];

ネットワークエラーなどでコンポーネントの読み込みに失敗した場合、エラーページを表示できます。

次の図は、遅延ロードによるバンドル分割の効果を示しています。

mermaidflowchart TB
    subgraph before["遅延ロード前"]
        bundle1["app.js<br/>(5MB)<br/>全コンポーネント含む"]
    end

    subgraph after["遅延ロード後"]
        bundle2["app.js<br/>(500KB)<br/>初期表示のみ"]
        bundle3["user.js<br/>(200KB)"]
        bundle4["admin.js<br/>(300KB)"]
        bundle5["products.js<br/>(400KB)"]
    end

    before --> after

    style bundle1 fill:#ffcccc
    style bundle2 fill:#ccffcc

トランジションの実装

ページ遷移にトランジションを追加することで、ユーザー体験が大きく向上します。

基本的なトランジション設定

まず、ルータービューをトランジションコンポーネントでラップします。

vue<!-- App.vue -->
<template>
  <router-view v-slot="{ Component }">
    <transition name="fade" mode="out-in">
      <component :is="Component" />
    </transition>
  </router-view>
</template>

次に、CSS でトランジションのスタイルを定義します。

css/* フェードトランジション */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

この設定により、ページ切り替え時にフェードイン・フェードアウトのアニメーションが適用されます。

スライドトランジション

より動きのあるスライドアニメーションを実装してみましょう。

vue<!-- App.vue -->
<template>
  <router-view v-slot="{ Component, route }">
    <transition name="slide" mode="out-in">
      <component :is="Component" :key="route.path" />
    </transition>
  </router-view>
</template>

CSS でスライドアニメーションを定義します。

css/* スライドトランジション */
.slide-enter-active,
.slide-leave-active {
  transition: all 0.3s ease;
}

.slide-enter-from {
  transform: translateX(100%);
  opacity: 0;
}

.slide-leave-to {
  transform: translateX(-100%);
  opacity: 0;
}

ルートごとに異なるトランジション

ルートのメタ情報を使用して、ページごとに異なるトランジションを適用できます。

typescript// router/index.ts
const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/Home.vue'),
    meta: { transition: 'fade' },
  },
  {
    path: '/about',
    name: 'About',
    component: () => import('../views/About.vue'),
    meta: { transition: 'slide' },
  },
];

コンポーネント側で動的にトランジション名を切り替えます。

vue<!-- App.vue -->
<template>
  <router-view v-slot="{ Component, route }">
    <transition
      :name="route.meta.transition || 'fade'"
      mode="out-in"
    >
      <component :is="Component" :key="route.path" />
    </transition>
  </router-view>
</template>

ネストされたトランジション

親子関係のあるルートで、それぞれ異なるトランジションを適用できます。

vue<!-- layouts/MainLayout.vue -->
<template>
  <div class="layout">
    <header>ヘッダー</header>
    <main>
      <router-view v-slot="{ Component }">
        <transition name="nested-slide" mode="out-in">
          <component :is="Component" />
        </transition>
      </router-view>
    </main>
  </div>
</template>

ネストされたトランジション用の CSS を定義します。

css/* ネストされたスライドトランジション */
.nested-slide-enter-active,
.nested-slide-leave-active {
  transition: all 0.2s ease;
}

.nested-slide-enter-from {
  transform: translateY(20px);
  opacity: 0;
}

.nested-slide-leave-to {
  transform: translateY(-20px);
  opacity: 0;
}

JavaScript フックを使用した高度なトランジション

複雑なアニメーションには、JavaScript フックと GSAP などのライブラリを組み合わせます。

vue<!-- App.vue -->
<script setup lang="ts">
import { gsap } from 'gsap';

function onBeforeEnter(el: Element) {
  gsap.set(el, {
    opacity: 0,
    y: 50,
  });
}

function onEnter(el: Element, done: () => void) {
  gsap.to(el, {
    opacity: 1,
    y: 0,
    duration: 0.5,
    ease: 'power2.out',
    onComplete: done,
  });
}

function onLeave(el: Element, done: () => void) {
  gsap.to(el, {
    opacity: 0,
    y: -50,
    duration: 0.3,
    ease: 'power2.in',
    onComplete: done,
  });
}
</script>

<template>
  <router-view v-slot="{ Component }">
    <transition
      mode="out-in"
      @before-enter="onBeforeEnter"
      @enter="onEnter"
      @leave="onLeave"
    >
      <component :is="Component" />
    </transition>
  </router-view>
</template>

カスタムトランジションクラスの使用

Animate.css などの外部ライブラリを使用する場合、カスタムクラス名を指定できます。

vue<template>
  <router-view v-slot="{ Component }">
    <transition
      mode="out-in"
      enter-active-class="animate__animated animate__fadeInRight"
      leave-active-class="animate__animated animate__fadeOutLeft"
    >
      <component :is="Component" />
    </transition>
  </router-view>
</template>

事前に Animate.css をインストールして読み込む必要があります。

bashyarn add animate.css
typescript// main.ts
import 'animate.css';

次の図は、トランジションのライフサイクルを示しています。

mermaidstateDiagram-v2
    [*] --> BeforeEnter: 入場開始
    BeforeEnter --> Enter: enter-from適用
    Enter --> EnterActive: enter-active適用
    EnterActive --> Entered: enter-to適用
    Entered --> [*]: 入場完了

    [*] --> BeforeLeave: 退出開始
    BeforeLeave --> Leave: leave-from適用
    Leave --> LeaveActive: leave-active適用
    LeaveActive --> Left: leave-to適用
    Left --> [*]: 退出完了

統合実装例

最後に、ガード、遅延ロード、トランジションを組み合わせた実践的な例を見てみましょう。

ルーター設定の完全版

すべての機能を統合したルーター設定です。

typescript// router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import type { RouteRecordRaw } from 'vue-router';

// ルート定義
const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/Home.vue'),
    meta: {
      transition: 'fade',
      title: 'ホーム',
    },
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('../views/Login.vue'),
    meta: {
      transition: 'slide',
      title: 'ログイン',
    },
  },
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: () =>
      import(
        /* webpackChunkName: "dashboard" */
        /* webpackPrefetch: true */
        '../views/Dashboard.vue'
      ),
    meta: {
      requiresAuth: true,
      transition: 'fade',
      title: 'ダッシュボード',
    },
    beforeEnter: (to, from, next) => {
      // ダッシュボード固有のチェック
      const hasAccess = checkDashboardAccess();
      hasAccess ? next() : next({ name: 'Home' });
    },
  },
];

// ルーターインスタンス
const router = createRouter({
  history: createWebHistory(),
  routes,
});

// グローバルガード
router.beforeEach((to, from, next) => {
  const requiresAuth = to.matched.some(
    (record) => record.meta.requiresAuth
  );
  const isAuthenticated = checkAuth();

  if (requiresAuth && !isAuthenticated) {
    next({
      name: 'Login',
      query: { redirect: to.fullPath },
    });
  } else {
    next();
  }
});

router.afterEach((to) => {
  document.title = `${to.meta.title || 'ページ'} | My App`;
});

function checkAuth(): boolean {
  return localStorage.getItem('token') !== null;
}

function checkDashboardAccess(): boolean {
  const userRole = localStorage.getItem('userRole');
  return userRole === 'admin' || userRole === 'user';
}

export default router;

この統合実装により、安全で高速、かつ美しいページ遷移が実現できます。

まとめ

Vue.js Router のナビゲーションガード、遅延ロード、トランジションは、モダンな SPA を構築するための必須技術です。

ナビゲーションガードにより、ページ遷移の各段階で認証チェックやデータプリロードを実行でき、安全で効率的なルーティングを実現できます。7 種類のガードを適切に使い分けることで、グローバルな制御からコンポーネント固有の処理まで、柔軟に対応できるでしょう。

遅延ロードは、アプリケーションのパフォーマンスを劇的に向上させます。動的 import と Webpack のマジックコメントを活用することで、初期バンドルサイズを削減し、ユーザーが必要とするコードだけを効率的に配信できますね。特に大規模アプリケーションでは、適切なコード分割が成功の鍵となります。

トランジションは、技術的な機能以上に、ユーザー体験の質を左右する重要な要素です。CSS トランジション、JavaScript フック、外部ライブラリの活用により、ブランドの個性を表現する洗練されたアニメーションを実装できるでしょう。

これら 3 つの機能を組み合わせることで、高速で安全、そして美しいユーザー体験を提供する Vue.js アプリケーションを構築できます。この記事の速見表と実装例を参考に、ぜひ実践してみてください。

関連リンク