1. Home
  2. JavaScript
  3. Vue.js
  4. VueRouterの基本とルーティングを構築するはじめの一歩

VueRouterの基本とルーティングを構築するはじめの一歩

  • 公開日
  • 更新日
  • カテゴリ:Vue.js
  • タグ:Vue,Routing,VueCLI,VueRouter,SPA,VueHead,SEO
VueRouterの基本とルーティングを構築するはじめの一歩

Vue.js で SPA を開発していくにあたり URL 履歴イベントをブラウザ内に構築する為の VueRouter というライブラリが提供されています。

今回は、VueRouter を用いた Vue.js アプリケーション開発の基本(ルーティングなど)を見ていきます。

Contents

  1. 開発環境
  2. なぜ VueRouter なのか
  3. プロジェクトのセットアップ
  4. ベーシックルーティング
  5. 動的ルートマッチング
  6. catch-all フォールバック
  7. 全マッチのルーティング
  8. 各種 meta タグの設置と切り替え
    1. vue-head
    2. title や meta の切り替え
  9. Google インデックス検証
  10. ナビゲーションガード

開発環境

今回の開発環境は以下の通りです。

  • Vue.js 2.x
  • Vue CLI 3.x
  • Node.js 12.x
  • npm 6.5

今回は、Vue Router の公式ガイドの内容にも少し沿いながら進めるので、用語など少し似たようにしています。 道中詳細な説明が足りないと感じたら、都度公式ガイドを参照してみてください。

なぜ VueRouter なのか

  • SPA(シングルページアプリケーション)は単一ページでのアプリケーションのため、操作的には機能や画面を切り替えられても URL 的な履歴を持つことが出来なかった。
  • HTML5 から、ブラウザの履歴操作機能として HTML5 History API が提供された。
  • VueRouter を用いることで URL 履歴イベントをブラウザ内に構築する事ができる。
  • ブラウザで「戻る」「進む」の操作が行え、ユーザビリティを向上させる事ができる。
  • 各画面や機能を個々のページとして認識させる事ができ、SEO 的にも有利になる。

プロジェクトのセットアップ

Vue CLI を用いてプロジェクトのセットアップを行います。

まずは対話形式でプロジェクトを作成していきます。以下の vue コマンドを叩きます。

# プロジェクトの作成
vue create app

ここからは対話型で進んで行きますが、Vue Router 導入に必要な箇所のみ抜粋します。

? Please pick a preset:
default (babel, eslint)
❯ Manually select features

Manually select features を選択し ENTER を押下します。

? Check the features needed for your project:
◉ Babel
  ◯ TypeScript
  ◯ Progressive Web App (PWA) Support
❯ ◉ Router
  ◯ Vuex
◉ CSS Pre-processors
◉ Linter / Formatter
  ◯ Unit Testing
  ◯ E2E Testing

Router をスペースキー押下でアクティブにし ENTER を押下します。

? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) Y

ヒストリーモードを On にするので Y を 入力し ENTER を押下します。

Vue Router に関する設定はこれだけなので、あとは好みの設定で進めてください。 プロジェクト作成に関しての詳細は以下でも紹介しています。
Vue CLI でプロジェクトを作成

インストールが完了したら、以下のコマンドを叩いて環境を立ち上げます。

# プロジェクトルートへ移動
cd app

# 開発サーバ起動
npm run serve

起動したらブラウザからアクセスします。 これで開発サーバが立ち上がったので、ここから Vue Router を使っていきます。

ベーシックルーティング

まずは最も基本的なルーティングから。

表示させるためだけのコンポーネントをひとまず3つ用意しておきます。

views/Home.vue, Page1.vue, Page2.vue
// views/Home.vue
<template>
  <div class="home">
    <h1>Home</h1>
  </div>
</template>

// views/Page1.vue
<template>
  <div class="page1">
    <h1>Page1</h1>
  </div>
</template>

// views/Page2.vue
<template>
  <div class="page2">
    <h1>Page2</h1>
  </div>
</template>

ルーティングを定義します。

router.js
import Vue from 'vue'
import Router from 'vue-router'
// コンポーネントを読み込む
import Home from './views/Home.vue'
import Page1 from './views/Page1.vue'
import Page2 from './views/Page2.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',      // このパスにアクセスしたら
      name: 'home',
      component: Home // このコンポーネントを呼ぶ
    },
    {
      path: '/page1',
      name: 'page1',
      component: Page1
    },
    {
      path: '/page2',
      name: 'page2',
      component: Page2
    }
  ]
})

ナビゲーション部分です。

src/App.vue
<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/page1">page1</router-link> |
      <router-link to="/page2">page2</router-link>
    </div>
    <router-view/>
  </div>
</template>

<style lang="scss">
  #nav {
    a {
      color: #42b983;
      text-decoration: none;
    }
    .router-link-exact-active {
      color: #00252b;
    }
  }
</style>
  • router-link コンポーネントを使ってナビゲーションを定義します。
  • to プロパティで遷移先を指定します。
  • router-view コンポーネントによって、ルーティングで定義されたコンポーネントが描画されます。

router-link の定義には色々なバリエーションで行う事ができます。
https://router.vuejs.org/ja/api/#router-link-props

一通り実装したので、ブラウザから確認してみます。
サンプルページ
ナビゲーションのリンクを押下すると画面を切り替える事ができます。
また、何回か遷移した後で「戻る」や「進む」をしてみると、履歴に沿って遷移する事を確認できました。

動的ルートマッチング

ルーティング時に URL パラメータの値を取りたい。そんな時のルーティングです。

router.js
routes: [
  {
    path: '/',
    name: 'home',
    component: Home
  },
  {
    path: '/page1/:param1',
    name: 'page1',
    component: Page1
  },
  {
    path: '/page2/:param1/:param2/:param3',
    name: 'page2',
    component: Page2
  }
]

:パラメータ名で取得するパラメータを指定します。 page2 のように、複数指定する事もできます。

src/App.vue
<div id="nav">
  <router-link to="/">Home</router-link> |
  <router-link to="/page1/1">page1</router-link> |
  <router-link to="/page2/2/hoge/20">page2</router-link>
</div>

ナビゲーション部分の to に、静的、または動的にパラメータをセットします。

views/Page1.vue views/Page2.vue

<!-- Page1.vue -->
<template>
  <div class="page1">
    <h1>Page1</h1>
    <p>{{ $route.params.param1 }}</p>
  </div>
</template>

<!-- Page2.vue -->
<template>
  <div class="page2">
    <h1>Page2</h1>
    <p>{{ $route.params.param1 }}</p>
    <p>{{ $route.params.param2 }}</p>
    <p>{{ $route.params.param3 }}</p>
  </div>
</template>

コンポーネントでは上記のようにして受け取ったパラメータを表示させる事が出来ます。 また、script 内で使用したい場合も以下のようにして取得する事ができます。

this.$route.params.param1

ブラウザから確認してみます。

サンプルページ

URL からパラメータを取得できた事を確認できました。

catch-all フォールバック

ここで一つの問題に直面しました。 アプリケーションのルート”/”(今回の例では Home)以外(Page1/Page2)を表示させた状態でリロードを行うと 404 エラーになってしまいます。 さらにリロードと同じく、直接 URL を入力してブラウザからアクセスしても同様に 404 エラーです。

これは、このアプリケーションが SPA であり、/index.html のみが実体として存在しているだけなので、単純にアクセス先が無い状態であるからです。

これを解消するには、SSR(サーバーサイドレンダリング)を用いるか、またはプリレンダリングで全ての機能を予め書き出してしまうか、 もしくは catch-all フォールバックで 404 を任意のページで受け止める方法で解決出来ます。

SSR やプリレンダリングはまた別の機会に触れるとして、今回は catch-all フォールバックでこの問題を解消してみます。

catch-all フォールバックについては、公式ガイドでは以下のようにサーバーの設定例が紹介されています。
VueRouter 公式ガイド - catch-all フォールバック サーバーの設定例

今回は Apache の設定を行ってみます。

mod_rewrite を用いたパターン
<VirtualHost *:80>
    ServerName your.domain.com
    DocumentRoot /path/to/document_root
    DirectoryIndex index.html
    ErrorLog /var/log/httpd/error.log
    CustomLog /var/log/httpd/access.log common

    <Directory "/path/to/document_root">
        AllowOverride All
        PostsListOrder Allow,Deny
        Allow from all

        # 追加
        <IfModule mod_rewrite.c>
            RewriteEngine On
            RewriteBase /
            RewriteRule ^index\.html$ - [L]
            RewriteCond %{REQUEST_FILENAME} !-f
            RewriteCond %{REQUEST_FILENAME} !-d
            RewriteRule . /index.html [L]
        </IfModule>
    </Directory>
</VirtualHost>

RewriteBase はディレクトリごとの設定ファイルでのみ有効なので、こんな感じでしょうか。

FallbackResource を用いたパターン
<VirtualHost *:80>
    ServerName your.domain.com
    DocumentRoot /path/to/document_root
    DirectoryIndex index.html
    ErrorLog /var/log/httpd/error.log
    CustomLog /var/log/httpd/access.log common

    <Directory "/path/to/document_root">
        AllowOverride All
        PostsListOrder Allow,Deny
        Allow from all

        # 追加
        FallbackResource /index.html
    </Directory>
</VirtualHost>

こっちは1行で済むのは手っ取り早い感じはします。

こんな感じで設定を行えば、リロードや直接アクセスしても index.html に流れるので 404 エラーにはならなくなります。

でもこれだけだとちょっと簿妙です。 できれば、ルーティング定義している URL についてはリロードや直接アクセスの場合にこの挙動でも良いのですが、 定義していない URL の場合は素直に 404 ページに遷移させたい。

そこで次の「全マッチのルーティング」が使えます。

全マッチのルーティング

全マッチのルーティングは、ルーティングで定義していない全ての URL へのリクエストをキャッチするルーティングです。

仮の 404 ページを用意して、そこへ定義済みではない全てのルーティングを渡すようにしてみます。

views/errors/404.vue
<template>
  <h1>404 Not found</h1>
</template>
router.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import Page1 from './views/Page1.vue'
import Page2 from './views/Page2.vue'
import NotFound from './views/errors/404.vue' // 追加

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/page1/:param1',
      name: 'page1',
      component: Page1
    },
    {
      path: '/page2/:param1/:param2/:param3',
      name: 'page2',
      component: Page2
    },
    { // 追加
      path: '*',
      name: 'notFound',
      component: NotFound

    }
  ]
})
  • 作成した 404.vue を import します。
  • path をアスタリスクで指定し、NotFound コンポーネントをセットします。

これで、これまでに定義した3つのルーティング以外のリクエストは全て NotFound コンポーネントへ向きます。

router.js で定義するルーティングは、上に定義されたものほど優先度が高くなります。(上から評価されていく)

こうする事で SPA でも、直接のアクセスやリロードに対応しつつ、定義してない URL に対しては 404 ページを表示する事が可能になりました。
サンプルページ
※サンプルページはリロード非対応

各種 meta タグの設置と切り替え

今回は Vue.js + VueRouter での開発になりますが、ルーティングを行い画面を切り替えられるようにしたら、各々のタイトルや description などの meta要素 も切り替えたいところです。

今回は vue-head というライブラリを導入し meta タグの設置を行い、それを VueRouter 切り替えていくという流れでいきたいと思います。

vue-head

こんなふうに小規模アプリケーションを作成していて思うのが、Vue CLI でビルドを行うとデフォルトでは meta タグは title くらいしか生成されないので、 たとえば OGP のタグや description のタグを挿入したい場合にあれこれする必要があってちょっと手間です。

vue-head を使うとその辺を簡単にしてくれるのでおすすめです。

vue-head
https://github.com/ktquez/vue-head

インストール

vue-head のインストールを行います。プロジェクトルートで以下 npm コマンドを叩きます。

npm install vue-head

meta タグの設置

まずは基本的な meta タグの設置を行います。 切り替えたいものも含め、まずはここでデフォルトを定義します。

src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import VueHead from 'vue-head' // 追加

Vue.use(VueHead) // 追加

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App),
  // 追加
  head: {
    meta: [
      {name: 'description', content: 'デフォルトの description です。'},
      {property: 'og:type', content: 'website'},
      // Twitter
      { name: 'twitter:title', content: 'Content Title' },
      // with shorthand
      { n: 'twitter:description', c: 'Content description less than 200 characters'},
      // Facebook / Open Graph
      { property: 'fb:app_id', content: '123456789' },
      { property: 'og:title', content: 'Content Title' },
      // with shorthand
      { p: 'og:image', c: 'https://xxxx.xx/image.jpg' },
    ],
  }
}).$mount('#app')

どんな項目を定義できるのかは以下から確認できます。
https://github.com/ktquez/vue-head#examples-new-syntax

これだけで、まずは meta タグが設置されます。

title や meta の切り替え

次に、title タグや meta を画面の切り替えと共に変更させていきます。 今回は、title タグと description を切り替えます。

まずはルーティング部分に、渡したい meta 情報を定義します。

src/router.js
routes: [
  {
    path: '/',
    name: 'home',
    component: Home,
    meta: { title: 'Home', desc: 'This is Home page.' } // 追加
  },
  {
    path: '/page1',
    name: 'page1',
    component: Page1,
    meta: { title: 'Page1', desc: 'This is Page1 page.' } // 追加
  },
  {
    path: '/page2',
    name: 'page2',
    component: Page2,
    meta: { title: 'Page2', desc: 'This is Page2 page.' } // 追加
  },
  {
    path: '/page3',
    name: 'page3',
    component: Page3,
    meta: { title: 'Page3', desc: 'This is Page3 page.' } // 追加
  },
  {
    path: '*',
    name: 'notFound',
    component: NotFound
  }
]

次に、router のルートコンポーネントに値をセットする処理を追加します。

src/App.vue
<script>
  /* 追加 */
  export default {
      methods : {
          setMeta (to) {
              if(to.meta.title){
                  document.title = to.meta.title;
              }
              if(to.meta.desc){
                  document.querySelector("meta[name='description']").setAttribute('content', to.meta.desc)
              }
          }
      },
      watch: {
          '$route' (to) {
              this.setMeta(to);
          }
      }
  }
</script>

ブラウザからアクセスして確認してみます。
サンプルページ

ルーティングでの切り替えによって、タイトルや meta タグが切り替わっている事が確認できました。

Google インデックス検証

ここで、分けた画面分、問題なく Google にインデックスされるかを検証してみました。

別ページとしては認識されましたが、タイトルが意図通りにインデックスされませんでした。

「app 」となっているのはデフォルトで設定したタイトルです。また、Page3 だけはタイトルが表示されていますが、これはおそらく設定したタイトルではなく、h1 タグから拾われたものであると推測できます。

これはおそらく切り替えるタイミングの問題で、現在のような、通常の切り替えでは遅いのでしょう。 という事で、ナビゲーションガードを利用して、そこで切り替えるように変更します。

ナビゲーションガード

ナビゲーションガードは、ナビゲーション解決フローの中で発火するライフサイクルメソッドです。 ナビゲーション開始から終了までの間でそれぞれ決められたメソッドが発火していきます。

例えば機能の切り替え要求が走った際に、機能を切り替える前に何らかの値を評価し意図通りではなかったら切り替えを行わないなどの処理を 挟んだりできます。

今回はここを利用して、適切なタイミングでタイトルなど meta 内容の切り替えを行います。

ナビゲーションガード
https://router.vuejs.org/ja/guide/advanced/navigation-guards.html

問題は「いつ」書き換えを行うかですが、description の切り替えは DOM 操作を行っているので、切り替える機能(画面)の DOM が構築されてからその処理を挟みたいところです。

という事で、結果がわかりやすいようにまずは切り替えたいタイトルなどの文言を、改めて定義し直します。

router.js
routes: [
  {
    path: '/',
    name: 'home',
    component: Home,
    meta: { title: 'verification apps', desc: 'This is Home page.' }
  },
  {
    path: '/page1',
    name: 'page1',
    component: Page1,
    meta: { title: 'Page1', desc: 'This is Page1 page.' }
  },
  {
    path: '/page2',
    name: 'page2',
    component: Page2,
    meta: { title: 'Page2', desc: 'This is Page2 page.' }
  },
  {
    path: '/page3',
    name: 'page3',
    component: Page3,
    meta: { title: 'Page3', desc: 'This is Page3 page.' }
  },
  {
    path: '*',
    name: 'notFound',
    component: NotFound
  }
]

次に、コンポーネント内ガードである beforeRouteEnter で meta 書き換えの処理を行います。 ナビゲーションのライフサイクルメソッドの中で beforeRouteEnter を選んだのは、DOM 書き換えを行っているのでコンポーネントがアクティブになってから処理を走らせる必要がある(そうでないと書き換え対象が見つからず処理が失敗する)為です。 また、これらは各コンポーネントに適用させる為、スクリプトを Mixin で切り出します。

views/mixins/MetaSettable
export default {
    beforeRouteEnter (to, from, next) {
        next(() => {
            if(to.meta.title){
                document.title = to.meta.title;
            }
            if(to.meta.desc){
                document.querySelector("meta[name='description']").setAttribute('content', to.meta.desc)
            }
        })
    }
}

先に定義してあった App.vue のスクリプトは不要なので除去してしまいます。

App.vue
<script>
  // 不要なので除去
  /*
  export default {
      methods : {
          setMeta (to) {
              if(to.meta.title){
                  document.title = to.meta.title;
              }
              if(to.meta.desc){
                  document.querySelector("meta[name='description']").setAttribute('content', to.meta.desc)
              }
          }
      },
      watch: {
          '$route' (to) {
              this.setMeta(to);
          }
      }
  }
  */
</script>

最後に、各コンポーネントで Mixin を読み込みます。

Home.vue & Page1 〜 3.vue
<script>
  import Meta from './mixins/MetaSettable'

  export default {
      mixins: [Meta]
  }
</script>

これで切り替えのタイミングを調整できたので再ビルドし、改めて Google へのインデックス検証を行いました。

意図通りに Google へインデックスさせる事ができました。

まとめ

今回は環境構築と基本的な使い方から、ちょっとした検証までを行いました。 VueRouter の基本的な機能はまだたくさんあるので、また別の機会にまとめるつもりです。

サンプルコード

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級