初心に戻ってNuxt.js＋Vuetify ルーティングを学び直す

# はじめに

私はVue.js ＋ Vue-routerで開発経験はあります。
結構、複雑なルーティングもVue-routerの機能を駆使して実現してきました。

今回、Nuxt.js ＋ Vuetifyで開発をはじめてみて、Nuxt.js/Vuetifyというフレームワークのお作法みたいなものが見えて来たのでまとめて見ようと思います。

一番は、`/user/<user_id>/friend`や`/user/<user_id>/friend/<friend_id>`などのパスを生成する方法を確認したかったというのがあります。

あと、以下のサンプルソースは、ローカルでの開発環境で記載しています。実際にレンタルサーバーにデプロイした場合に、リンク切れなどの問題が発生しました。

そちらは、[Nuxt.jsのSPAモードのファイルをレンタルサーバーにデプロイした時にリンク切れが起きる場合の対処法](https://qiita.com/idani/items/ff5c7c3cd7b5c91df917)で記載しています。

# 前提

今回、サンプルとして作成したプロジェクトは、以下の設定となっています。
UIフレームワークとして、`Vuetify.js`を選択しています。

![2020-04-12_10h33_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/bfbbece1-7df3-7bd2-027f-37003c181277.png)

# 基礎知識

作成したプロジェクトで`yarn dev`しますと、`http://localhost:3000/`で以下の画面が表示されます。

![2020-04-12_10h40_19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/91d803e3-e20a-63c4-e5e0-cd6e704f4d1b.png)

自動生成されたフォルダリストは、下記の画像にあるとおりです。
そして、初期表示されるページは、`pages/index.vue`ファイルになります。

![2020-04-12_10h42_42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/38c338cf-7672-ecb2-641a-488d27d41ab4.png)

Nuxt.jsは、ディレクトリ構造からルーティングファイルを自動生成しています。
この自動生成されたファイルがどこにあるかというと、`.nuxt`フォルダの中の`router.js`になります。

![2020-04-12_10h43_50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/cf43a0c0-9ebd-ee4f-fe03-faa243d1d907.png)

```javascript:.nuxt/router.js
import Vue from 'vue'

~~~省略~~~

const _6ab8c138 = () => interopDefault(import('..\\pages\\inspire.vue' /* webpackChunkName: "pages_inspire" */))
const _a0f196a0 = () => interopDefault(import('..\\pages\\index.vue' /* webpackChunkName: "pages_index" */))

~~~省略~~~

  routes: [{
    path: "/inspire",
    component: _6ab8c138,
    name: "inspire"
  }, {
    path: "/",
    component: _a0f196a0,
    name: "index"
  }],

  fallback: false
}

~~以下省略~~~

```
`routes`配列の中にある `path: "/"`に対応するコンポーネントは`_a0f196a0`です。
この`_a0f196a0`が何かというと、`import('..\\pages\\index.vue'`に対応しています。

このファイルの構成については、[Vue-routerのはじめに](https://router.vuejs.org/ja/guide/)の項目なので、ここがわからない方は、一読されるとよいです。

# ルーティングの基礎

それではNux.jsのドキュメントの[ルーティングの基礎](https://ja.nuxtjs.org/guide/routing#%E3%83%AB%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E3%81%AE%E5%9F%BA%E7%A4%8E)に戻って作っていきましょう。

`user`ディレクトリを作成し、その中に`index.vue`と`one.vue`を作成します。
![2020-04-12_11h09_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/07779f95-47f4-b0e1-a18a-aa2c635ed58b.png)

```vue:pages/user/index.vue

<template>
  <div>/user/index.vue</div>
</template>

<script>
export default {
  name: 'index'
}
</script>

<style scoped></style>
```

```vue:pages/user/one.vue
<template>
  <div>/user/one.vue</div>
</template>

<script>
export default {
  name: 'index'
}
</script>

<style scoped></style>
```

`yarn dev`をしていると`pages/user/index.vue`と`pages/user/one.vue`が新規に作成されたことを自動検知して、`.nuxt/router.js`が自動更新されます。

```javascript:.nuxt/router.js
~~~省略~~~

const _6ab8c138 = () =>
  interopDefault(
    import('..\\pages\\inspire.vue' /* webpackChunkName: "pages_inspire" */)
  )
const _7e712405 = () =>
  interopDefault(
    import(
      '..\\pages\\user\\index.vue' /* webpackChunkName: "pages_user_index" */
    )
  )
const _7ee72b4e = () =>
  interopDefault(
    import('..\\pages\\user\\one.vue' /* webpackChunkName: "pages_user_one" */)
  )
const _a0f196a0 = () =>
  interopDefault(
    import('..\\pages\\index.vue' /* webpackChunkName: "pages_index" */)
  )

~~~ 省略~~~

  routes: [
    {
      path: '/inspire',
      component: _6ab8c138,
      name: 'inspire'
    },
    {
      path: '/user',
      component: _7e712405,
      name: 'user'
    },
    {
      path: '/user/one',
      component: _7ee72b4e,
      name: 'user-one'
    },
    {
      path: '/',
      component: _a0f196a0,
      name: 'index'
    }
  ],

  fallback: false
}

~~~省略~~~
}
```
`path: '/user'`に対応するcomponentは`_7e712405`となっており、変数`_7e712405`を探すと、`'..\\pages\\user\\index.vue'`をimportしたものになっています。

同じく、`path: '/user/one'`に対応するcomponentは`_7ee72b4e`となっており、こちらは、`'..\\pages\\user\\one.vue'`をimportしたものになっています。

実際にブラウザで`http://localhost:3000/user/`を開くと`pages/user/index.vue`が表示されました。

![2020-04-12_11h23_26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/1ff2aad1-6a1f-17d2-3986-6a2cfd800d39.png)


同じくブラウザで`http://localhost:3000/user/one`を開くと`pages/user/one.vue`が表示されました。

![2020-04-12_11h23_40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/37a8c91e-2018-860d-f511-94e2a630b5de.png)


さて、ここで確認して欲しいことがあります。

> http://localhost:3000/user/
> http://localhost:3000/user

このようにURLの末尾にスラッシュがありなしでも、`pages/user/index.vue`が表示されます。

同じく、`pages/user/one.uve`でも、末尾にスラッシュありなしでも表示されます。

> http://localhost:3000/user/one
> http://localhost:3000/user/one/


つまり、**特定のパスを表示したい場合は、`パスを示すファイル`を作成するか、`パスを示すフォルダ+index.vue`を作成すれば良い**ことがわかります。

ここまでのソースは、[github](https://github.com/idani/nuxt-router-test1/tree/%E3%83%AB%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E3%81%AE%E5%9F%BA%E7%A4%8E)で確認ができます。

# 動的なルーティング

次に、[動的なルーティング](https://ja.nuxtjs.org/guide/routing#%E5%8B%95%E7%9A%84%E3%81%AA%E3%83%AB%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0)を見ていきましょう。

冒頭に書かれています、この文章、大切です。

> 動的なルーティングを定義するには .vue ファイル名またはディレクトリ名に アンダースコアのプレフィックス を付ける必要があります。

# アンダースコアのプレフィックスのファイルを作成する

まずは、**アンダースコアのプレフィックスのファイル**として、`pages/user/_id.vue`作成します。

よくある画面イメージは管理画面で、`index.vue`でユーザ一覧、`_id.vue`で各ユーザの詳細を表示します。

まとめると以下の機能構成になります。

| URL    |       ファイル |    機能    |
|:-----------------|:------------------|:------------------|
| /user             |/user/index.vue |ユーザ一覧|
| /user/\<user_id\>   |/user/_id.vue|各ユーザの詳細画面|


## `pages/user/index.vue`の修正

まずは、`pages/user/index.vue`を以下のように書き換えます。

以下は掲載するには長いので一部省略をしています。
全部閲覧したい方は、Githubの[こちら](https://github.com/idani/nuxt-router-test1/blob/master/pages/user/index.vue)を参照してください。

```vue:pages/user/index.vue
<template>
  <v-simple-table>
    <template v-slot:default>
      <thead>
        <tr>
          <th class="text-left" width="50">id</th>
          <th class="text-left">Name</th>
          <th width="200"></th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="user in users" :key="user.id">
          <td class="text-right">{{ user.id }}</td>
          <td>{{ user.name }}</td>
            <v-btn color="primary" small :to="`user/${user.id}`">
              <v-icon left>mdi-account-details</v-icon>
              詳細
            </v-btn>
        </tr>
      </tbody>
    </template>
  </v-simple-table>
</template>

<script>
export default {
  name: 'index',
  data() {
    return {
      users: [
        {
          name: 'Frozen Yogurt',
          id: 159
        },
~~~省略~~~        
  }
}
</script>

<style scoped></style>

```
Vuetifyの[シンプル・テーブル](https://vuetifyjs.com/ja/components/simple-tables/#%E3%82%B7%E3%83%B3%E3%83%97%E3%83%AB%E3%83%BB%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB)のサンプルを流用して、ユーザ一覧を作成しました。

そしてユーザの詳細画面にリンクをするために、以下のように`v-btn`を使った**詳細ボタン**を追加しています。

```vue
<v-btn color="primary" small :to="`user/${user.id}`">
  <v-icon left>mdi-account-details</v-icon>
  詳細
</v-btn>
```

ちなみにVuetifyでは、リンクの作成は`to`プロパティで行います。
[v-btnのAPI](https://vuetifyjs.com/ja/components/buttons/#api)の`to`プロパティを確認すると、
**vue-router**の[`to`プロパティ](https://router.vuejs.org/ja/api/#to)に対応しています。

![2020-04-12_12h29_50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/39f228da-45da-4f3e-1a7b-729d7c310d47.png)

**vue-router**の[`to`プロパティ](https://router.vuejs.org/ja/api/#to)を確認すると、`to`として、以下のように記述ができるということですが、今回の目的の`/user/<user_id>`を実現するには、**名前付きルート**が良さそうです。

```vue
<!-- 文字列 -->
<router-link to="home">Home</router-link>
<!-- 次のように描画される -->
<a href="home">Home</a>

<!-- `v-bind` を使った javascript 式-->
<router-link v-bind:to="'home'">Home</router-link>

<!-- 他のプロパティのバインディングのような `v-bind` の省略表現 -->
<router-link :to="'home'">Home</router-link>

<!-- 上記と同じ -->
<router-link :to="{ path: 'home' }">Home</router-link>

<!-- 名前付きルート -->
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>

<!-- 結果的に `/register?plan=private` になるクエリ-->
<router-link :to="{ path: 'register', query: { plan: 'private' }}">Register</router-link>

```

ところが、Nuxt.jsでは、ルーティングは自動生成されるため、**名前付きルート**を利用する`name`は、`.nuxt/router.js`を参照して確認する必要があり面倒です。


この結果、リンクの記述方法は、**相対パスを直接記述する**スタイルがよさそうなので、``:to="`user/${user.id}`"``として、`pages/user/_id.vue`にリンクをしています。

これで`http://localhost:3000/user`にアクセスをすると以下の表示になりました。
![2020-04-12_12h20_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/264066d6-5829-0bac-ed85-d5ba484e912d.png)

**詳細ボタン**にマウスを持ってくると、ブラウザの一番下に、以下のように`localhost:3000/user/<user_id>`というリンク先が表示されました。


![2020-04-12_12h21_46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/66007e26-04d4-bc2e-fea4-61aded2cca54.png)![2020-04-12_12h22_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/68685cf2-436d-e2f8-51ce-6abd80d602fe.png)


## `pages/user/_id.vue`を作成

次に、以下のように`pages/user/_id.vue`を作成します。
![2020-04-12_11h40_31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/61e0125c-b444-887f-2a62-4d2767cc2b2a.png)

```vue:pages/user/_id.vue
<template>
  <div>/user/{{ userId }}</div>
</template>

<script>
export default {
  name: 'UserDetail',
  computed: {
    userId() {
      return this.$route.params.id
    }
  }
}
</script>

<style scoped></style>

```

`pages/user/_id.vue`を作成したら、自動生成された`.nuxt/router.js`を確認します。

```javascript:.nuxt/router.js
~~~省略~~~
const _7e712405 = () =>
  interopDefault(
    import(
      '..\\pages\\user\\index.vue' /* webpackChunkName: "pages_user_index" */
    )
  )
const _699263ad = () =>
  interopDefault(
    import('..\\pages\\user\\_id.vue' /* webpackChunkName: "pages_user__id" */)
  )
~~~省略~~~
　// Routes配列は見やすいように不要なオブジェクトを削除しています。
 routes: [
    {
      path: '/user',
      component: _7e712405,
      name: 'user'
    },
    {
      path: '/user/:id',
      component: _699263ad,
      name: 'user-id'
    },
  ],
~~~省略~~~

```
`path: '/user/:id'`は、`'..\\pages\\user\\_id.vue'`に対応付されていることがわかります。

`_id.vue`を作成しましたので、各ユーザの詳細ボタンをクリックしたら、詳細画面に表示されるようになりました。
![2020-04-12_13h05_09.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/a582cb81-ba11-a7b5-1b50-ee8f1aeeae61.gif)
※戻るときは、ブラウザのバックボタンで戻っています。

## ルーティングパラメータのバリデーション

これでユーザ一覧ページからユーザ詳細ページを閲覧する画面ができました。

さて、想像して見ください。

この場合、勘がいい管理者さんでしたら、`http://localhost/user/<user_id>`としてユーザの詳細ページが表示されますから、直接入力をしたくなります。

それでは、`user_id = abc`として、`http://localhost:3000/user/abc`をブラウザに入力をしてみましょう。
![2020-04-12_13h16_34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/d689f822-8317-8f2c-08a8-44d61183a091.png)
何事もなく表示されてしまいました(汗)

ユーザIDは、数値として制限したいです。
この場合に、[ルーティングのパラメータのバリデーション](https://ja.nuxtjs.org/guide/routing#%E3%83%AB%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E3%81%AE%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%81%AE%E3%83%90%E3%83%AA%E3%83%87%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)を記述します。

公式ドキュメントの通り`_id.vue`に`validate`を追加します。

```vue:pages/user/_id.vue
<template>
  <div>/user/{{ userId }}</div>
</template>

<script>
export default {
  name: 'UserDetail',
  validate({ params }) {
    // 数値でなければならない
    return /^\d+$/.test(params.id)
  },
  computed: {
    userId() {
      return this.$route.params.id
    }
  }
}
</script>

<style scoped></style>
```
この結果、`http://localhost:3000/user/abc`をリロードすると、`404 Not Found`になりました。
![2020-04-12_13h20_46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/a460de5f-4f0a-bbf8-a773-7d8d5625415b.png)

## ルーティングパラメータの取得

ルーティングパラメータとは、URLに含まれる情報になります。
Nuxt.jsの公式ドキュメントの[ルートパラメータへのローカルアクセス](https://ja.nuxtjs.org/guide/routing#%E3%83%AB%E3%83%BC%E3%83%88%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%81%B8%E3%81%AE%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9)を見ると、`this.$route.params.{parameterName}`を参照できました。

今回は、`user_id`が取得したいので、`computed`に記載しています。

```vue
  computed: {
    userId() {
      return this.$route.params.id
    }
  }
```
余談ですが、vue-routerの公式ドキュメントの[ルートコンポーネントにプロパティを渡す](https://router.vuejs.org/ja/guide/essentials/passing-props.html#%E3%83%AB%E3%83%BC%E3%83%88%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%AB%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%82%92%E6%B8%A1%E3%81%99)には、以下のように記載されています。

> コンポーネントで $route を使うとコンポーネントとルートの間に密結合が生まれ、コンポーネントが特定のURLでしか使用できないなど柔軟性が制限されます。コンポーネントをルーターから分離するために props オプションを使います

Nuxt.jsの場合も、`props`で渡せないか試してみましたが、**できませんでした。**

Nuxt.jsの便利な機能を使うためのトレードオフとして割り切るしかないですね。

以上のソースは、[github上のtag:アンダースコアプレフィックスファイル](https://github.com/idani/nuxt-router-test1/tree/%E3%82%A2%E3%83%B3%E3%83%80%E3%83%BC%E3%82%B9%E3%82%B3%E3%82%A2%E3%83%97%E3%83%AC%E3%83%95%E3%82%A3%E3%83%83%E3%82%AF%E3%82%B9%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB)で参照ができます。

# アンダースコアプレフィックスをつけたディレクトリ

次にシチューエーションとしては、Facebookを想像して、各ユーザが友達となっている人の一覧、友達の詳細画面を作成するとします。

URLとしては、以下を考えます。

| URL    |       ファイル |    機能    |
|:-----------------|:------------------|:------------------|
| /user             |/user/index.vue |ユーザ一覧|
| /user/\<user_id\>   |/user/_id.vue|各ユーザの詳細画面|
| /user/\<user_id\>/friend   |？|各ユーザの友達一覧|
| /user/\<user_id\>/friend/\<friend_id\>   |？|各ユーザの友達詳細|

ファイルはどうしたらよいでしょうか？

ここで一番初めに紹介した言葉を思い出します。

>パラメータを使って動的なルーティングを定義するには **.vue ファイル名またはディレクトリ名に アンダースコアのプレフィックス を付ける**必要があります。

`_id.vue`をアンダースコアプレフィックスを付けたフォルダにすればよいのです。
以下のように書き換えていきます。


| URL    |       ファイル |    機能    |
|:-----------------|:------------------|:------------------|
| /user             |/user/index.vue |ユーザ一覧|
| /user/\<user_id\>   |/user/_id/index.vue|各ユーザの詳細画面|
| /user/\<user_id\>/friend|/user/_id/friend/index.vue|各ユーザの友達一覧|
| /user/\<user_id\>/friend/\<friend_id\>|/user/\_id/friend/\_fId.vue|各ユーザの友達詳細|

## _id.vueを_id/index.vueに書き換える。

まずは、`_id.vue`を`_id/index.vue`に書き換えます。
![2020-04-12_13h38_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/259b1758-4f36-49a1-f51a-8b06aa55558f.png)

自動更新後の`.nuxt/router.js`を確認すると、`path: '/user/:id'`は、`..\\pages\\user\\_id\\index.vue'`に対応付けられていることがわかります。

```javascript:.nuxt/router.js
~~~省略~~~
const _5805e435 = () =>
  interopDefault(
    import(
      '..\\pages\\user\\_id\\index.vue' /* webpackChunkName: "pages_user__id_index" */
    )
  )
~~~省略~~~
routes: [
    {
      path: '/user/:id',
      component: _5805e435,
      name: 'user-id'
    },
  ],
~~~省略~~~
```

一応、ブラウザでも動作確認をして、動作として問題ないことを確認します。
![2020-04-12_13h05_09.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/a582cb81-ba11-a7b5-1b50-ee8f1aeeae61.gif)

## 友達一覧ページの作成

友達一覧を表示するURLは、`/user/<user_id>/friend`としました。
`pages/user/_id/friend`ディレクトリを作成後、以下のように`pages/user/_id/firiend/index.vue`を作成します。
![2020-04-12_13h48_33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/e5362aad-2dca-33c9-c74d-e888b7990fd1.png)

```vue:pages/user/_id/friend/index.vue
<template>
  <v-card>
    <v-card-title>
      <h1>ユーザID: {{ userId }}の友達一覧</h1>
    </v-card-title>
    <v-card-actions>
      <v-btn small color="primary" :to="`/user/${userId}`">戻る</v-btn>
    </v-card-actions>

    <v-card-text>
      <v-simple-table>
        <template v-slot:default>
          <thead>
            <tr>
              <th class="text-left" width="50">fid</th>
              <th class="text-left">Name</th>
              <th width="200"></th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="user in users" :key="user.id">
              <td class="text-right">{{ user.id }}</td>
              <td>{{ user.name }}</td>
              <td>
                <v-btn color="primary" small :to="`user/${user.id}`">
                  <v-icon left>mdi-account-details</v-icon>
                  詳細
                </v-btn>
              </td>
            </tr>
          </tbody>
        </template>
      </v-simple-table>
    </v-card-text>
  </v-card>
</template>

<script>
export default {
  name: 'index',
  validate({ params }) {
    // 数値でなければならない
    return /^\d+$/.test(params.id)
  },
  data() {
    return {
      users: [
        {
          name: 'Frozen Yogurt',
          id: 159
        },
~~~省略~~~
      ]
    }
  },
  computed: {
    userId() {
      return this.$route.params.id
    }
  }
}
</script>

<style scoped></style>

```

ブラウザに`http://localhost:3000/user/159/friend`と入力をすると、以下のような友達一覧が表示されました。
![2020-04-12_13h58_42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/71e2fcc0-fe9a-d12a-18dc-035449dda8be.png)

さて、`ユーザ詳細`に戻るために`戻るボタン`をつけています。
![2020-04-12_14h03_49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/131c0192-3ce9-2e0f-32ae-4179fbd0831e.png)

`ユーザ詳細`のURLは`/user/<user_id>`です、
`友達一覧`のURLは、`/user/<user_id>/friend`です。
リンクとしては、1階層上に上がることになります。

パスの記法として、1階層下がる場合は、相対リンクで記述ができるのですが、1階層上がるということを相対リンクで記述する方法がわからないため、以下のような絶対リンクで記述しています。

```vuejs
<v-btn small color="primary" :to="`/user/${userId}`">戻る</v-btn>
```

絶対リンクですと、後々パスの周りで不具合がでそうです。名前付きルートで記載した方が良いかもしれませんね。

ただ、先に書いたようにルート名は、Nuxtで自動生成されます。なので、Nuxtの自動生成ルールを覚えておくか、毎回、`.nuxt/router.js`を確認する必要があります。このあたり、どうしたらよいかな？って思案しています。

## ユーザ詳細画面から友達一覧にリンクを追加する

`友達一覧`から`ユーザ詳細`にリンクができましたので、逆に`ユーザ詳細`から`友達一覧`にリンクを作成します。

以下のように、`pages/user/_id/index.vue`の`<template>`を書き変えます。

```vuejs:pages/user/_id/index.uve
<template>
  <v-card>
    <v-card-title>
      <h1>ユーザ: {{ userId }}</h1>
    </v-card-title>
    <v-card-actions>
      <v-btn color="primary" to="friend" append>
        <v-icon left>mdi-account-group</v-icon>友達一覧
      </v-btn>
    </v-card-actions>
  </v-card>
</template>
```

書き換え後の画面は以下になります。
![2020-04-12_14h15_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/8ded586c-e6e4-17c2-1e77-e54432b2284b.png)


余談ですが、`<template>`を見ていただいてわかりますが、`v-card`を使ってレイアウトしています。
`Vuetify`を使う場合は、[`v-card`](https://vuetifyjs.com/ja/components/cards/)をパーツブロックのように使っていくのがよいように思います。

ポイントは、`友達一覧`ページへのリンクの記述方法になります。
現在のURLにサブディレクトリ名を追加したい場合は、`to`に`サブディレクトリ名`を追加し、さらに`append`プロパティを追加します。

```vue
<v-btn color="primary" to="friend" append>
```
![2020-04-12_14h18_53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/c29b0b37-5f02-6d8e-a765-42375d687aff.png)
※Vuetifyの公式ドキュメントの[v-btnのプロパティ](https://vuetifyjs.com/ja/components/buttons/#api)を参照

これでユーザ詳細ページから友達一覧ページの相互の行き来ができるようになりました。
![2020-04-12_14h21_19.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/93584f17-cf37-7fdc-26be-da2030d75f10.gif)

## 友達詳細ページ

最後に、`友達詳細`を作成して、`友達一覧`からリンクを作成し、相互に行き来できるようにします。

以下のように`pages/user/_id/friend/_fid.vue`を作成します。

```vue:pages/user/_id/friend/_fid.vue
<template>
  <v-card>
    <v-card-title>
      <h1>ユーザ: {{ userId }}の友達: {{ friendId }}の詳細</h1>
    </v-card-title>
    <v-card-actions>
      <v-btn color="primary" :to="`/user/${userId}/friend`">
        <v-icon left>mdi-account-group</v-icon>友達一覧に戻る
      </v-btn>
    </v-card-actions>
  </v-card>
</template>

<script>
export default {
  name: 'friendDetail',
  validate({ params }) {
    // 数値でなければならない
    return /^\d+$/.test(params.id)
  },
  computed: {
    userId() {
      return this.$route.params.id
    },
    friendId() {
      return this.$route.params.fid
    }
  }
}
</script>

<style scoped></style>
```
また、`pages/user/_id/index.vue`の友達詳細のリンク部分を修正します。

```vue:pages/user/_id/index.vue
<v-btn color="primary" small :to="`${user.id}`" append>
  <v-icon left>mdi-account-details</v-icon>
  友達詳細
</v-btn>
```

![2020-04-12_14h50_30.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/5f1e6274-489a-12f9-4215-c0a00f64d60f.gif)


以上のソースは、[github上のtag:アンダースコアプレフィックスフォルダ](https://github.com/idani/nuxt-router-test1/tree/%E3%82%A2%E3%83%B3%E3%83%80%E3%83%BC%E3%82%B9%E3%82%B3%E3%82%A2%E3%83%97%E3%83%AC%E3%83%95%E3%82%A3%E3%83%83%E3%82%AF%E3%82%B9%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80)で参照ができます。

# ネストされたルート

次は[ネストされたルート](https://ja.nuxtjs.org/guide/routing#%E3%83%8D%E3%82%B9%E3%83%88%E3%81%95%E3%82%8C%E3%81%9F%E3%83%AB%E3%83%BC%E3%83%88)を考えます。

公式ドキュメントにネストについて、以下のように記載されています。

>ネストされたルートの親コンポーネントを定義するには、子ビューを含む ディレクトリと同じ名前 の Vue ファイルを作成する必要があります。

さきほどまでのサンプルの自動生成された`.nuxt/router.js`を確認すると、以下のようになっていてネストしていません。

```javascript:.nuxt/router.js
~~~省略~~
//以下、一部routesの中身を省略
 routes: [
    {
      path: '/user',
      component: _7e712405,
      name: 'user'
    },
    {
      path: '/user/:id',
      component: _5805e435,
      name: 'user-id'
    },
    {
      path: '/user/:id/friend',
      component: _22619457,
      name: 'user-id-friend'
    },
    {
      path: '/user/:id/friend/:fid',
      component: _1f41cd66,
      name: 'user-id-friend-fid'
    },
    {
      path: '/',
      component: _a0f196a0,
      name: 'index'
    }
  ],
~~~省略~~~
```
試しに、`friend`をネストさせてみましょう。
ネストさせるには、`friend`フォルダと同じ名前の`friend.vue`ファイルを作成します。
![2020-04-12_15h25_19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/db1f0bd2-ecfd-1b5b-5790-8e548494ecf8.png)

```vue:pages/_id/friend.vue
<template>
  <div></div>
</template>

<script>
export default {
  name: 'friend'
}
</script>

<style scoped></style>

```

そして自動生成されたroute.jsを確認します。
`routes`配列の`path: '/user/:id/friend'`がネストされているのがわかります。

```javascript:.nuxt/router.js
~~~省略~~~
　// 配列の中身を一部削除しています。
  routes: [
    {
      path: '/user/:id/friend',
      component: _1619b5cb,
      children: [
        {
          path: '',
          component: _22619457,
          name: 'user-id-friend'
        },
        {
          path: ':fid',
          component: _1f41cd66,
          name: 'user-id-friend-fid'
        }
      ]
    }
  ],
~~~省略~~~

```

さて、`ユーザ詳細`から`友達一覧`のリンクはどうなったでしょうか？

![2020-04-12_15h36_35.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/5b34be5c-46d5-bc9d-9a5a-eef15c382243.gif)

予想通り、`友達一覧`が表示されなくなりました。

[Nuxt.jsの公式ドキュメントの警告](https://ja.nuxtjs.org/guide/routing#%E3%83%8D%E3%82%B9%E3%83%88%E3%81%95%E3%82%8C%E3%81%9F%E3%83%AB%E3%83%BC%E3%83%88)を抜かしていました。

> 警告: \<nuxt-child/\> を親コンポーネント内（.vue ファイル内）に書くことを忘れないでください。

以下のように`<template>`の`<nuxt-child />`を追加しました。

```vue:pages/user/_id/friend.vue
<template>
  <div>
    <nuxt-child />
  </div>
</template>

<script>
export default {
  name: 'friend'
}
</script>

<style scoped></style>

```
これで元通りになりました。
![2020-04-12_15h41_14.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/9f200953-5e89-f1a9-936d-072f23def672.gif)

ただ、これではネストしてもネストしなくても動作が変わらないので、ネストしたありがたみがわかりません。

個人的には、ネストした場合のありがたみは、friendフォルダのファイルに専用のちょっとしたレイアウトを追加できることだと思います。

以下のように、`pages/user/_id/friend.vue`を書き換えます。

```vue:pages/user/_id/friend.vue
<template>
  <v-card>
    <v-card-title>
      <h1>ユーザ: {{ userId }}</h1>
    </v-card-title>
    <v-card-text>
      <nuxt-child />
    </v-card-text>
  </v-card>
</template>

<script>
export default {
  name: 'friend',
  computed: {
    userId() {
      return this.$route.params.id
    }
  }
}
</script>

<style scoped></style>

```
これで以下のような画面にかわります。
![2020-04-12_15h46_57.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133291/42cd9932-ceea-484f-24f4-facd339ecb7d.gif)

`friend/index.vue`と`friend/_fid.vue`で、`ユーザID`を表示していましたが、これが不要になりますね。


以上のソースは、[githubのtag:ネストされたルート](https://github.com/idani/nuxt-router-test1/tree/%E3%83%8D%E3%82%B9%E3%83%88%E3%81%95%E3%82%8C%E3%81%9F%E3%83%AB%E3%83%BC%E3%83%88)にあります。

# 最後に

Nuxt.jsのルーティングの自動生成の挙動を確認するためにサンプルを作成してみました。

[動的ルーティング](https://ja.nuxtjs.org/guide/routing#%E5%8B%95%E7%9A%84%E3%81%A7%E3%83%8D%E3%82%B9%E3%83%88%E3%81%95%E3%82%8C%E3%81%9F%E3%83%AB%E3%83%BC%E3%83%88)と[未知の動的でネストされたルート](https://ja.nuxtjs.org/guide/routing#%E6%9C%AA%E7%9F%A5%E3%81%AE%E5%8B%95%E7%9A%84%E3%81%A7%E3%83%8D%E3%82%B9%E3%83%88%E3%81%95%E3%82%8C%E3%81%9F%E3%83%AB%E3%83%BC%E3%83%88)は公式サイトのドキュメントで分かりづらい部分はないので、割愛しています。

これがNuxt.jsのルーティングの理解の一助になれば幸いです。



こういうのがライブコーディングで、スラスラかければ動画にするんですけどね(苦笑)