RitoLabo

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

  • 公開:
  • 更新:
  • カテゴリ: JavaScript Vue.js
  • タグ: Vue,Routing,VueCLI,VueRouter,SPA,VueHead,SEO

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

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

アジェンダ
  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
views/Page1.vue
views/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
Order 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
Order 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コンポーネントへ向きます。

404NotFoundページへのルーティング

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タグが設置されます。

vue-headで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タグが切り替わっている事が確認できました。

metaタグ切り替え確認

Googleインデックス検証

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

Googleインデックス結果1回目

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

意図通りにならなかったインデックス

「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インデックス結果

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

まとめ

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

サンプルコード